ot-ios-builder 0.7.9.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,82 @@
1
+ ## 0.7.5
2
+ * Allows automation of version/build number setting
3
+ * Adds full paths to xcodebuild and xcrun; they are override-able in the config
4
+ * Adds ability to add arguments to package command
5
+ * Refactors commands and argument code
6
+ * Makes build and signing output optional (use config.verbose to trigger)
7
+ * Adds simplified output
8
+ * Allows custom SCP ports (@smtlaissezfaire)
9
+ * Fixes Archive task failure (@rennarda)
10
+ * Uses Apple's Packager to produce valid IPAs (@dts)
11
+ * Fixes cases where CFPropertyList could fail to load (@svelix)
12
+ * Uses relative paths for requires (@smtlaissezfaire)
13
+ * Raises an exception if the build fails (@epall)
14
+
15
+
16
+ ## 0.7.4.1
17
+ * Allow auto-archiving from other Rake namespaces (@victor)
18
+ * Fixed bug with Xcode archive sharing (@victor)
19
+ * Fall back on CFBundleVersion if CFBundleVersionShortString is not set (@subdigital)
20
+ * Added verbose and dry run options to the TestFlight deployment strategy (@subdigital)
21
+ * Defer dynamic release note generation until runtime (@subdigital)
22
+ * Allow architectures to be configured (@subdigital)
23
+ * Fix detection of build directories containing spaces
24
+
25
+ # 0.7.4
26
+ * YANKED
27
+
28
+ ## 0.7.3
29
+ * Made the Xcode derived data directory more robust by grepping for the Validate line in the log.
30
+
31
+ ## 0.7.2
32
+ * Handle missing values in Xcode archive plist.
33
+
34
+ ## 0.7.1
35
+ * Fixed a problem with detecting the Xcode 4 derived data directory.
36
+
37
+ ## 0.7.0
38
+ * Much improved Xcode 4 support.
39
+ * Generate Xcode 4 style archives using the :xcode4_archive_mode
40
+ * Xcode 4 style archives appear in Xcode organiser complete with your release notes.
41
+ * Added a :skip_clean option to skip cleaning when building
42
+ * Allow the app name to be explicitly set using :app_name
43
+ * WARNING: Xcode 3 support is officially deprecated as of this release!
44
+
45
+ ## 0.6.0
46
+ * Support Xcode 4 workspaces and schemes.
47
+
48
+ ## 0.5.0
49
+ * Support configurable path to xcodebuild executable
50
+ * Support configurable path to Xcode project file
51
+
52
+ ## 0.4
53
+ * If the user has set the EDITOR environment variable, use it to obtain the TestFlight release notes.
54
+ * Bugfix: TestFlight API now returns a 201 Created response when successful.
55
+ * Bugfix: Handle spaces in build artefacts.
56
+ * Updated the default archived build location to match the newest Xcode archived build location.
57
+
58
+ ## 0.3.2
59
+ * Fixed bug #2 (task fails when no testflight distribution list set)
60
+
61
+ ## 0.3.1
62
+ * Separate the :deploy task into :prepare and :deploy tasks.
63
+
64
+ ## 0.3
65
+ * Added support for distribution lists to the TestFlight deployment strategy
66
+
67
+ ## 0.2.1
68
+ * Allow the namespace of generated tasks to be customised
69
+
70
+ ## 0.2
71
+ * Introduced deployment strategies, allowing custom deployment methods
72
+ * Added support for deploying beta releases to TestFlightApp.com
73
+
74
+ ## 0.1.2
75
+
76
+ * Allow custom hosts when using the SCP deployment task (simonjefford)
77
+
78
+ ## 0.1.1
79
+ * Fixed missing dependency
80
+
81
+ ## 0.1
82
+ * Initial Release
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2010 Luke Redpath
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # BetaBuilder, a gem for managing iOS ad-hoc builds
2
+
3
+ BetaBuilder is a simple collection of Rake tasks and utilities for managing and publishing Adhoc builds of your iOS apps.
4
+
5
+ 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).
6
+
7
+ **Note: As of release 0.7, support for Xcode 3 is deprecated. Xcode 4 has been out for a year, has got much more stable as of 4.1 on Lion (or 4.2 if you've been using the betas) and it's time to move on. Generating Xcode 4 friendly archives and builds in this release still needs a bit of configuration but will become much smoother in 0.8 once Xcode 3 support is removed.**
8
+
9
+ ## Motivation
10
+
11
+ The problem with using a GUI app to create the beta packages is that it is yet another manual step in the process of producing an ad-hoc build for your beta testers. It simplifies some steps but it still requires running Build and Archive in Xcode, saving the resulting build as an IPA package, running the Beta Builder app, locating the IPA, filling in the rest of the fields and generating the deployment files. Then you need to upload those files somewhere.
12
+
13
+ As a Ruby developer, I use Rake in most of my projects to run repetitive, often build or test-related tasks and it's equally as useful for non-Ruby projects as it is for Ruby ones.
14
+
15
+ This simple task library allows you to configure once and then build, package and distribute your ad-hoc releases with a single command.
16
+
17
+ ## Usage
18
+
19
+ To get started, if you don't already have a Rakefile in the root of your project, create one. If you aren't familiar with Rake, it might be worth [going over some of the basics](http://rake.rubyforge.org/) but it's fairly straightforward.
20
+
21
+ You can install the BetaBuilder gem from your terminal (OSX 10.6 ships with a perfectly useful Ruby installation):
22
+
23
+ $ gem install betabuilder
24
+
25
+ At the top of your Rakefile, you'll need to require `rubygems` and the `betabuilder` gem (obviously).
26
+
27
+ require 'rubygems'
28
+ require 'betabuilder'
29
+
30
+ 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:
31
+
32
+ BetaBuilder::Tasks.new do |config|
33
+ # your Xcode target name
34
+ config.target = "MyGreatApp"
35
+
36
+ # the Xcode configuration profile
37
+ config.configuration = "Adhoc"
38
+ end
39
+
40
+ 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:
41
+
42
+ rake beta:build # Build the beta release of the app
43
+ rake beta:package # Package the beta release as an IPA file
44
+
45
+ If you use a custom Xcode build directory, rather than the default `${SRCROOT}/build` location, you can configure that too:
46
+
47
+ BetaBuilder::Tasks.new do |config|
48
+ ...
49
+ config.build_dir = "/path/to/custom/build/dir"
50
+ end
51
+
52
+ To deploy your beta to your testers, some additional configuration is needed (see the next section).
53
+
54
+ 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.
55
+
56
+ If you are not using the automatic deployment task, you will need to upload the contents of the pkg/dist directory to your server.
57
+
58
+ To use a namespace other than "beta" for the generated tasks, simply pass in your chosen namespace to BetaBuilder::Tasks.new:
59
+
60
+ BetaBuilder::Tasks.new(:my_custom_namespace) do |config|
61
+ end
62
+
63
+ This lets you set up different sets of BetaBuilder tasks for different configurations in the same Rakefile (e.g. a production and staging build).
64
+
65
+ ## Configuration
66
+ A full list of configuration options and their details
67
+
68
+ `configuration` - (String) The Xcode Configuration to use (Defined on the Info tab of the Project)
69
+
70
+ `build_dir` - (File Path) The directory the build output will be. (`:derived` for Xcode 4 for versions < 0.8)
71
+
72
+ `auto_archive` - (true/**false**) Automatically archive when packaging
73
+
74
+ `archive_path` - (File Path) Path to the Archives
75
+
76
+ `xcodebuild_path` - (File Path) Path to xcodebuild, if its non-standard
77
+
78
+ `xcodeargs` - (Arguments) Arguments passed to `xcodebuild` when it runs
79
+
80
+ `project_file_path` - (File Path) Path to the `.xcodeproj` file
81
+
82
+ `workspace_path` - (File Path) Path to the `.xcworkspace` file
83
+
84
+ `ipa_destination_path` - (File Path) Path to output Packaged IPA to
85
+
86
+ `scheme` - (String) What Scheme to use when building
87
+
88
+ `app_name` - (String) The name of the app being built (should match the file name, less the `.app` extension)
89
+
90
+ `arch` - (String) The architecture to build for, if different from project settings
91
+
92
+ `xcode4_archive_mode` - (true/**false**) Use Xcode4's Archive mode
93
+
94
+ `skip_clean` - (true/**false**) Skip the clean step when building
95
+
96
+ `verbose` - (true/**false**) Increased output for debugging
97
+
98
+ `dry_run` - (true/**false**) Don't upload to Distribution Strategy, just act like it
99
+
100
+ `set_version_number` - (true/**false**) Attempts to set a version number, see below
101
+
102
+ ### Configuration (Testflight)
103
+ Testflight presents its own set of options that can be configured
104
+
105
+ `api_token` - (String) Can be your API key, but its recommended to use an `ENV[""]` variable
106
+
107
+ `team_token` - (String) Your Team's Testflight API token
108
+
109
+ `distribution_lists` - (Array) A Ruby array (`[1,2]` or `%w{1 2}`) of distribution list names for Testflight
110
+
111
+ `notify` - (true/**false**) Notify the distribution list of this build
112
+
113
+ `replace` - (true/**false**) Replace if an existing build exists with the same ID and version
114
+
115
+ ### Configuration (Web)
116
+ Pushing to a web server has the following options.
117
+
118
+ SSH keys will simplify authentication and make this process seamless
119
+
120
+ `remote_host` - (String) Hostname for the server the build will be pushed to
121
+
122
+ `remote_port` - (String) Port Number to use for SCP/SFTP
123
+
124
+ `remote_installation_path` - (String) Remote Path
125
+
126
+ ### Configuration (RunTime)
127
+ Certain configuration options are availabe at the command line, so that you can temporarily set them for a single run without modifying your configuration.
128
+
129
+ Pass any of these in as environment variables:
130
+
131
+ `DRY` - (true/**false**) Enable Dry Run
132
+ `VERBOSE` - (true/**false**) Turn on all output; lets you see the clean, build, and signing output
133
+ `SKIPCLEAN` - (true/**false**) Skips the clean step and goes right to Build.
134
+
135
+ ####Examples
136
+
137
+ `rake staging:deploy DRY=true`
138
+
139
+ `rake staging:redeploy VERBOSE=true SKIPCLEAN=true`
140
+
141
+ ## Xcode 4 support
142
+
143
+ 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.
144
+
145
+ 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.
146
+
147
+ config.build_dir = :derived
148
+
149
+ This will become the default in 0.8.
150
+
151
+ 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):
152
+
153
+ config.xcode4_archive_mode = true
154
+
155
+ If you are working with an Xcode 4 workspace instead of a project file, you will need to configure this too:
156
+
157
+ config.workspace_path = "MyWorkspace.xcworkspace"
158
+ config.scheme = "My App Scheme"
159
+ config.app_name = "MyApp"
160
+ set_version_number
161
+ If you are using a workspace, then you must specify the scheme. You can still specify the build configuration (e.g. Release).
162
+
163
+ ## Automatic deployment with deployment strategies
164
+
165
+ 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.
166
+
167
+ ## Setting version numbers automatically
168
+
169
+ 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.
170
+
171
+ 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.
172
+
173
+ 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
174
+
175
+ config.set_version_number = true
176
+
177
+ It will generate a version number like: `1.0-15-g6b3c1bb`.
178
+
179
+ * *1.0* is the most recent tag
180
+ * *15* is the number of commits since the tag was generated
181
+ * *g6b3c1bb* is the beginning of the hash of the last commit.
182
+
183
+ ### Deploying your app with TestFlight
184
+
185
+ 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.
186
+
187
+ 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:
188
+
189
+ config.deploy_using(:testflight) do |tf|
190
+ tf.api_token = "YOUR_API_TOKEN"
191
+ tf.team_token = "YOUR_TEAM_TOKEN"
192
+ end
193
+
194
+ 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.
195
+
196
+ 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.
197
+
198
+ config.deploy_using(:testflight) do |tf|
199
+ ...
200
+ tf.generate_release_notes do
201
+ # return release notes here
202
+ end
203
+ end
204
+
205
+ Finally, you can also specify an array of distribution lists that you want to allow access to the build:
206
+
207
+ config.deploy_using(:testflight) do |tf|
208
+ ...
209
+ tf.distribution_lists = %w{Testers Internal}
210
+ end
211
+
212
+ ### Deploying to your own server
213
+
214
+ 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.
215
+
216
+ You will to configure betabuilder to use the `web` deployment strategy with some additional configuration:
217
+
218
+ config.deploy_using(:web) do |web|
219
+ web.deploy_to = "http://beta.myserver.co.uk/myapp"
220
+ web.remote_host = "myserver.com"
221
+ web.remote_directory = "/remote/path/to/deployment/directory"
222
+ end
223
+
224
+ 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.
225
+
226
+ ## License
227
+
228
+ This code is licensed under the MIT license.
229
+
230
+ Copyright (c) 2010 Luke Redpath
231
+
232
+ Permission is hereby granted, free of charge, to any person obtaining a copy
233
+ of this software and associated documentation files (the "Software"), to deal
234
+ in the Software without restriction, including without limitation the rights
235
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
236
+ copies of the Software, and to permit persons to whom the Software is
237
+ furnished to do so, subject to the following conditions:
238
+
239
+ The above copyright notice and this permission notice shall be included in
240
+ all copies or substantial portions of the Software.
241
+
242
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
243
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
244
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
245
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
246
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
247
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
248
+ THE SOFTWARE.
@@ -0,0 +1,435 @@
1
+ require 'rake/tasklib'
2
+ require 'ostruct'
3
+ require 'fileutils'
4
+ require 'cfpropertylist'
5
+ require File.dirname(__FILE__) + '/beta_builder/archived_build'
6
+ require File.dirname(__FILE__) + '/beta_builder/deployment_strategies'
7
+ require File.dirname(__FILE__) + '/beta_builder/release_strategies'
8
+ require File.dirname(__FILE__) + '/beta_builder/build_output_parser'
9
+
10
+ module BetaBuilder
11
+ class Tasks < ::Rake::TaskLib
12
+ def initialize(namespace = :beta, &block)
13
+ @configuration = Configuration.new(
14
+ :configuration => "Adhoc",
15
+ :build_dir => "build",
16
+ :xcodebuild_path => "/usr/bin/xcodebuild",
17
+ :xcrun_path => "/usr/bin/xcrun",
18
+ :xcodeargs => nil,
19
+ :packageargs => nil,
20
+ :project_file_path => nil,
21
+ :workspace_name => nil,
22
+ :workspace_path => nil,
23
+ :ipa_destination_path => "./pkg",
24
+ :zip_ipa_and_dsym => true,
25
+ :scheme => nil,
26
+ :app_name => nil,
27
+ :arch => nil,
28
+ :skip_clean => ENV.fetch('SKIPCLEAN', false),
29
+ :verbose => ENV.fetch('VERBOSE', false),
30
+ :sdk => "iphoneos",
31
+ :app_info_plist => nil,
32
+ :scm => nil,
33
+ :skip_scm_tagging => true,
34
+ :skip_version_increment => true,
35
+ :spec_file => nil
36
+ )
37
+ @namespace = namespace
38
+ yield @configuration if block_given?
39
+ define
40
+ end
41
+
42
+ def xcodebuild(*args)
43
+ # we're using tee as we still want to see our build output on screen
44
+ cmd = []
45
+ cmd << @configuration.xcodebuild_path
46
+ cmd.concat args
47
+ puts "Running: #{cmd.join(" ")}" if @configuration.verbose
48
+ cmd << "2>&1 %s build.output" % (@configuration.verbose ? '| tee' : '>')
49
+ cmd = cmd.join(" ")
50
+ system(cmd)
51
+ end
52
+
53
+ class Configuration < OpenStruct
54
+ def release_notes_text
55
+ return release_notes.call if release_notes.is_a? Proc
56
+ release_notes
57
+ end
58
+
59
+ def build_arguments
60
+ args = []
61
+ if workspace_path
62
+ raise "A scheme is required if building from a workspace" unless scheme
63
+ args << "-workspace '#{workspace_path}'"
64
+ args << "-scheme '#{scheme}'"
65
+ else
66
+ args << "-target '#{target}'"
67
+ args << "-project '#{project_file_path}'" if project_file_path
68
+ end
69
+
70
+ args << "-sdk #{sdk}"
71
+
72
+ args << "-configuration '#{configuration}'"
73
+ args << "-arch '#{arch}'" unless arch.nil?
74
+
75
+ if xcodeargs
76
+ args.concat xcodeargs if xcodeargs.is_a? Array
77
+ args << "#{xcodeargs}" if xcodears.is_a? String
78
+ end
79
+
80
+ args
81
+ end
82
+
83
+ def app_file_name
84
+ raise ArgumentError, "app_name or target must be set in the BetaBuilder configuration block" if app_name.nil? && target.nil?
85
+ if app_name
86
+ "#{app_name}.app"
87
+ else
88
+ "#{target}.app"
89
+ end
90
+ end
91
+
92
+ def app_info_plist_path
93
+ if app_info_plist != nil then
94
+ File.expand_path app_info_plist
95
+ else
96
+ nil
97
+ end
98
+ end
99
+
100
+ def build_number
101
+ # no plist is found, return a nil version
102
+ if (app_info_plist_path == nil) || (!File.exists? app_info_plist_path) then
103
+ return nil
104
+ end
105
+
106
+ # read the plist and extract data
107
+ plist = CFPropertyList::List.new(:file => app_info_plist_path)
108
+ data = CFPropertyList.native_types(plist.value)
109
+ data["CFBundleVersion"]
110
+ end
111
+
112
+ def next_build_number
113
+ # if we don't have a current version, we don't have a next version :)
114
+ if build_number == nil then
115
+ return nil
116
+ end
117
+
118
+ # get a hold on the build number and increment it
119
+ version_components = build_number.split(".")
120
+ new_build_number = version_components.pop.to_i + 1
121
+ version_components.push new_build_number.to_s
122
+ version_components.join "."
123
+ end
124
+
125
+ def built_app_long_version_suffix
126
+ if build_number == nil then
127
+ ""
128
+ else
129
+ "-#{build_number}"
130
+ end
131
+ end
132
+
133
+ def ipa_name
134
+ prefix = app_name == nil ? target : app_name
135
+ "#{prefix}#{built_app_long_version_suffix}.ipa"
136
+ end
137
+
138
+ def built_app_path
139
+ if build_dir == :derived
140
+ File.join("#{derived_build_dir}", "#{configuration}-#{sdk}", "#{app_file_name}")
141
+ else
142
+ File.join("#{build_dir}", "#{configuration}-#{sdk}", "#{app_file_name}")
143
+ end
144
+ end
145
+
146
+ def built_dsym_path
147
+ "#{built_app_path}.dSYM"
148
+ end
149
+
150
+ def derived_build_dir
151
+ for dir in Dir[File.join(File.expand_path("~/Library/Developer/Xcode/DerivedData"), "#{workspace_name}-*")]
152
+ return "#{dir}/Build/Products" if File.read( File.join(dir, "info.plist") ).match workspace_path
153
+ end
154
+ end
155
+
156
+
157
+ def derived_build_dir_from_build_output
158
+ output = BuildOutputParser.new(File.read("build.output"))
159
+ output.build_output_dir
160
+ end
161
+
162
+ def zipped_package_name
163
+ "#{app_name}#{built_app_long_version_suffix}.zip"
164
+ end
165
+
166
+ def ipa_path
167
+ File.join(File.expand_path(ipa_destination_path), ipa_name)
168
+ end
169
+
170
+ def dsym_name
171
+ "#{app_name}#{built_app_long_version_suffix}.dSYM.zip"
172
+ end
173
+
174
+ def dsym_path
175
+ File.join(File.expand_path(ipa_destination_path), dsym_name)
176
+ end
177
+
178
+ def app_bundle_path
179
+ "#{ipa_destination_path}/#{app_name}.app"
180
+ end
181
+
182
+ def deploy_using(strategy_name, &block)
183
+ if DeploymentStrategies.valid_strategy?(strategy_name.to_sym)
184
+ self.deployment_strategy = DeploymentStrategies.build(strategy_name, self)
185
+ self.deployment_strategy.configure(&block)
186
+ else
187
+ raise "Unknown deployment strategy '#{strategy_name}'."
188
+ end
189
+ end
190
+
191
+ def release_using(strategy_name, &block)
192
+ if ReleaseStrategies.valid_strategy?(strategy_name.to_sym)
193
+ self.release_strategy = ReleaseStrategies.build(strategy_name, self)
194
+ self.release_strategy.configure(&block)
195
+ self.release_strategy.prepare
196
+ else
197
+ raise "Unknown release strategy '#{strategy_name}'."
198
+ end
199
+ end
200
+ end
201
+
202
+ private
203
+
204
+ def define
205
+ namespace(@namespace) do
206
+ desc "Clean the Build"
207
+ task :clean do
208
+ unless @configuration.skip_clean
209
+ print "Cleaning Project..."
210
+ xcodebuild @configuration.build_arguments, "clean"
211
+ puts "Done"
212
+ end
213
+ end
214
+
215
+ desc "Build the beta release of the app"
216
+ task :build => :clean do
217
+ print "Building Project..."
218
+ xcodebuild @configuration.build_arguments, "build"
219
+ raise "** BUILD FAILED **" if BuildOutputParser.new(File.read("build.output")).failed?
220
+ puts "Done"
221
+ end
222
+
223
+ desc "Package the release as a distributable archive"
224
+ task :package => :build do
225
+ # there is no need for IPA or dSYM unless we have a device build,
226
+ # so do that part only on iphoneos SDKs
227
+ # likewise, there is no need to keep the .app folder around if we're building for ARM
228
+ # so skip this part on iphoneos SDK
229
+ if(@configuration.sdk.eql? "iphoneos") then
230
+ package_ipa
231
+ package_dsym
232
+ package_final_artifact
233
+ else
234
+ # clean the pkg folder: create it if it doesn't exist yet
235
+ FileUtils.mkdir_p @configuration.ipa_destination_path unless File.exists? @configuration.ipa_destination_path
236
+ # and remove an existing app_bundle_path if it exists
237
+ FileUtils.rm_rf @configuration.app_bundle_path unless !File.exists? @configuration.app_bundle_path
238
+
239
+ # now we can properly copy the app bundle path over.
240
+ FileUtils.cp_r @configuration.built_app_path, "#{@configuration.ipa_destination_path}"
241
+ end
242
+ end
243
+
244
+ desc "Builds an IPA from the built .app"
245
+ def package_ipa
246
+ print "Packaging and Signing..."
247
+ raise "** PACKAGE FAILED ** No Signing Identity Found" unless @configuration.signing_identity
248
+ # trash and create the dist IPA path if needed
249
+ FileUtils.rm_rf @configuration.ipa_destination_path unless !File.exists? @configuration.ipa_destination_path
250
+ FileUtils.mkdir_p @configuration.ipa_destination_path
251
+
252
+ # Construct the IPA and Sign it
253
+ cmd = []
254
+ cmd << @configuration.xcrun_path
255
+ cmd << "-sdk #{@configuration.sdk}"
256
+ cmd << "PackageApplication"
257
+ cmd << "-v '#{@configuration.built_app_path}'"
258
+ cmd << "-o '#{@configuration.ipa_path}'"
259
+ cmd << "--sign '#{@configuration.signing_identity}'"
260
+ cmd << "--embed '#{@configuration.provisioning_profile}'" unless @configuration.signing_identity == nil
261
+ if @configuration.packageargs then
262
+ cmd.concat @configuration.packageargs if @configuration.packageargs.is_a? Array
263
+ cmd << @configuration.packageargs if @configuration.packageargs.is_a? String
264
+ end
265
+ puts "Running #{cmd.join(" ")}" if @configuration.verbose
266
+ cmd << "2>&1 %s build.output" % (@configuration.verbose ? '| tee' : '>')
267
+ cmd = cmd.join(" ")
268
+ system(cmd)
269
+
270
+ # zip the dSYM over to the dist folder
271
+ puts "Done"
272
+ end
273
+
274
+ desc "Zips the dSYM to the package folder"
275
+ def package_dsym
276
+ print "Packaging dSYM..."
277
+
278
+ # copy the dSYM to the pkg destination
279
+ FileUtils.cp_r @configuration.built_dsym_path, @configuration.ipa_destination_path
280
+
281
+ # the version is pulled from a path relative location, so fetch BEFORE
282
+ # we Dir.chdir
283
+ dsym_name = @configuration.dsym_name
284
+ dsym_target_path = @configuration.dsym_path
285
+
286
+ # move to pkg destination and zip the dSYM
287
+ current_dir = Dir.pwd
288
+ Dir.chdir @configuration.ipa_destination_path
289
+
290
+ cmd = []
291
+ cmd << "zip"
292
+ cmd << "-r"
293
+ cmd << dsym_target_path
294
+ cmd << "#{@configuration.app_name}.app.dSYM"
295
+
296
+ puts "Running #{cmd.join(" ")}" if @configuration.verbose
297
+ cmd << "2>&1 %s ../build.output" % (@configuration.verbose ? '| tee' : '>')
298
+ cmd = cmd.join(" ")
299
+ system(cmd)
300
+ # back to working directory
301
+ Dir.chdir current_dir
302
+ end
303
+
304
+ desc "Packages the final artifact (IPA + dSYM)"
305
+ def package_final_artifact
306
+ # keep track of current working dir
307
+ current_dir = Dir.pwd
308
+ Dir.chdir @configuration.ipa_destination_path
309
+
310
+ # zip final package
311
+ cmd = []
312
+ cmd << "zip"
313
+ cmd << @configuration.zipped_package_name
314
+ cmd << @configuration.dsym_name
315
+ cmd << @configuration.ipa_name
316
+ cmd << "2>&1 %s ../build.output" % (@configuration.verbose ? '| tee' : '>')
317
+ system cmd.join " "
318
+
319
+ # delete all the artifacts but the .app. which will be needed by the automation builds
320
+ File.delete @configuration.dsym_name unless !File.exists? @configuration.dsym_name
321
+ FileUtils.rm_rf "#{@configuration.app_name}.app.dSYM" unless !File.exists? "#{@configuration.app_name}.app.dSYM"
322
+
323
+ # back to working directory
324
+ Dir.chdir current_dir
325
+
326
+ puts "Done"
327
+ puts "ZIP package: #{@configuration.zipped_package_name}"
328
+ end
329
+
330
+ desc "Tag SCM and prepares for next release (increments build number)"
331
+ task :release => :package do
332
+ release
333
+ end
334
+
335
+ desc "For CocoaPod libraries: Tags SCM, pushes to cocoapod and increments build number"
336
+ task :cocoapod_release => :build do
337
+ raise "CocoaPod repo is not set, aborting cocoapod_release task." unless @configuration.pod_repo != nil
338
+ raise "Spec file is not set, aborting cocoapod_release task." unless @configuration.spec_file != nil
339
+
340
+ # tag and push current pod
341
+ @configuration.release_strategy.tag_current_version
342
+ push_pod
343
+
344
+ # increment version numbers
345
+ if increment_pod_and_plist_number then
346
+ # and push appropriately
347
+ @configuration.release_strategy.prepare_for_next_pod_release
348
+ end
349
+ end
350
+
351
+ def push_pod
352
+ cmd = []
353
+ cmd << "pod"
354
+ cmd << "push"
355
+ cmd << @configuration.pod_repo
356
+ # cmd << "--local-only"
357
+ cmd << "--allow-warnings"
358
+
359
+ print "Pushing to CocoaPod..."
360
+ system (cmd.join " ")
361
+ puts "Done."
362
+ end
363
+
364
+ def increment_pod_and_plist_number
365
+ old_build_number = @configuration.build_number
366
+ if !prepare_for_next_release then
367
+ return false
368
+ end
369
+
370
+ # bump the spec version and save it
371
+ spec_content = File.open(@configuration.spec_file, "r").read
372
+ old_version = "version = '#{old_build_number}'"
373
+ new_version = "version = '#{@configuration.build_number}'"
374
+
375
+ spec_content = spec_content.sub old_version, new_version
376
+
377
+ File.open(@configuration.spec_file, "w") {|f|
378
+ f.write spec_content
379
+ }
380
+
381
+ true
382
+ end
383
+
384
+ if @configuration.deployment_strategy
385
+ desc "Prepare your app for deployment"
386
+ task :deploy_and_release => :package do
387
+ # deploy first
388
+ @configuration.deployment_strategy.deploy
389
+
390
+ # then release
391
+ release
392
+ end
393
+ end
394
+
395
+ def release
396
+ # tag first, then prepare for next release and
397
+ # commit the updated plist
398
+ if !@configuration.skip_scm_tagging then
399
+ @configuration.release_strategy.tag_current_version
400
+ end
401
+
402
+ if prepare_for_next_release then
403
+ @configuration.release_strategy.prepare_for_next_release
404
+ end
405
+ end
406
+
407
+ def prepare_for_next_release
408
+ if @configuration.skip_version_increment then
409
+ return false
410
+ end
411
+
412
+ next_build_number = @configuration.next_build_number
413
+ if next_build_number == nil then
414
+ return false
415
+ end
416
+
417
+ print "Updating #{@configuration.app_info_plist} to version #{next_build_number}"
418
+
419
+ # read the plist and extract data
420
+ plist = CFPropertyList::List.new(:file => @configuration.app_info_plist_path)
421
+ data = CFPropertyList.native_types(plist.value)
422
+
423
+ # re inject new version number into the data
424
+ data["CFBundleVersion"] = next_build_number
425
+
426
+ # recreate the plist and save it
427
+ plist.value = CFPropertyList.guess(data)
428
+ plist.save(@configuration.app_info_plist_path, CFPropertyList::List::FORMAT_XML)
429
+ puts "Done"
430
+ return true
431
+ end
432
+ end
433
+ end
434
+ end
435
+ end