smtlaissezfaire-betabuilder 0.7.4.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,67 @@
1
+ ## 0.7.4.1
2
+ * Allow auto-archiving from other Rake namespaces (@victor)
3
+ * Fixed bug with Xcode archive sharing (@victor)
4
+ * Fall back on CFBundleVersion if CFBundleVersionShortString is not set (@subdigital)
5
+ * Added verbose and dry run options to the TestFlight deployment strategy (@subdigital)
6
+ * Defer dynamic release note generation until runtime (@subdigital)
7
+ * Allow architectures to be configured (@subdigital)
8
+ * Fix detection of build directories containing spaces
9
+
10
+ # 0.7.4
11
+ * YANKED
12
+
13
+ ## 0.7.3
14
+ * Made the Xcode derived data directory more robust by grepping for the Validate line in the log.
15
+
16
+ ## 0.7.2
17
+ * Handle missing values in Xcode archive plist.
18
+
19
+ ## 0.7.1
20
+ * Fixed a problem with detecting the Xcode 4 derived data directory.
21
+
22
+ ## 0.7.0
23
+ * Much improved Xcode 4 support.
24
+ * Generate Xcode 4 style archives using the :xcode4_archive_mode
25
+ * Xcode 4 style archives appear in Xcode organiser complete with your release notes.
26
+ * Added a :skip_clean option to skip cleaning when building
27
+ * Allow the app name to be explicitly set using :app_name
28
+ * WARNING: Xcode 3 support is officially deprecated as of this release!
29
+
30
+ ## 0.6.0
31
+ * Support Xcode 4 workspaces and schemes.
32
+
33
+ ## 0.5.0
34
+ * Support configurable path to xcodebuild executable
35
+ * Support configurable path to Xcode project file
36
+
37
+ ## 0.4
38
+ * If the user has set the EDITOR environment variable, use it to obtain the TestFlight release notes.
39
+ * Bugfix: TestFlight API now returns a 201 Created response when successful.
40
+ * Bugfix: Handle spaces in build artefacts.
41
+ * Updated the default archived build location to match the newest Xcode archived build location.
42
+
43
+ ## 0.3.2
44
+ * Fixed bug #2 (task fails when no testflight distribution list set)
45
+
46
+ ## 0.3.1
47
+ * Separate the :deploy task into :prepare and :deploy tasks.
48
+
49
+ ## 0.3
50
+ * Added support for distribution lists to the TestFlight deployment strategy
51
+
52
+ ## 0.2.1
53
+ * Allow the namespace of generated tasks to be customised
54
+
55
+ ## 0.2
56
+ * Introduced deployment strategies, allowing custom deployment methods
57
+ * Added support for deploying beta releases to TestFlightApp.com
58
+
59
+ ## 0.1.2
60
+
61
+ * Allow custom hosts when using the SCP deployment task (simonjefford)
62
+
63
+ ## 0.1.1
64
+ * Fixed missing dependency
65
+
66
+ ## 0.1
67
+ * 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.
@@ -0,0 +1,156 @@
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
+ ## Xcode 4 support
66
+
67
+ 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.
68
+
69
+ 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.
70
+
71
+ config.build_dir = :derived
72
+
73
+ This will become the default in 0.8.
74
+
75
+ 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):
76
+
77
+ config.xcode4_archive_mode = true
78
+
79
+ If you are working with an Xcode 4 workspace instead of a project file, you will need to configure this too:
80
+
81
+ config.workspace_path = "MyWorkspace.xcworkspace"
82
+ config.scheme = "My App Scheme"
83
+ config.app_name = "MyApp"
84
+
85
+ If you are using a workspace, then you must specify the scheme. You can still specify the build configuration (e.g. Release).
86
+
87
+ ## Automatic deployment with deployment strategies
88
+
89
+ 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.
90
+
91
+ ### Deploying your app with TestFlight
92
+
93
+ 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.
94
+
95
+ 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:
96
+
97
+ config.deploy_using(:testflight) do |tf|
98
+ tf.api_token = "YOUR_API_TOKEN"
99
+ tf.team_token = "YOUR_TEAM_TOKEN"
100
+ end
101
+
102
+ 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.
103
+
104
+ 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.
105
+
106
+ config.deploy_using(:testflight) do |tf|
107
+ ...
108
+ tf.generate_release_notes do
109
+ # return release notes here
110
+ end
111
+ end
112
+
113
+ Finally, you can also specify an array of distribution lists that you want to allow access to the build:
114
+
115
+ config.deploy_using(:testflight) do |tf|
116
+ ...
117
+ tf.distribution_lists = %w{Testers Internal}
118
+ end
119
+
120
+ ### Deploying to your own server
121
+
122
+ 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.
123
+
124
+ You will to configure betabuilder to use the `web` deployment strategy with some additional configuration:
125
+
126
+ config.deploy_using(:web) do |web|
127
+ web.deploy_to = "http://beta.myserver.co.uk/myapp"
128
+ web.remote_host = "myserver.com"
129
+ web.remote_directory = "/remote/path/to/deployment/directory"
130
+ end
131
+
132
+ 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.
133
+
134
+ ## License
135
+
136
+ This code is licensed under the MIT license.
137
+
138
+ Copyright (c) 2010 Luke Redpath
139
+
140
+ Permission is hereby granted, free of charge, to any person obtaining a copy
141
+ of this software and associated documentation files (the "Software"), to deal
142
+ in the Software without restriction, including without limitation the rights
143
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
144
+ copies of the Software, and to permit persons to whom the Software is
145
+ furnished to do so, subject to the following conditions:
146
+
147
+ The above copyright notice and this permission notice shall be included in
148
+ all copies or substantial portions of the Software.
149
+
150
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
151
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
152
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
153
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
154
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
155
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
156
+ THE SOFTWARE.
@@ -0,0 +1,173 @@
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/build_output_parser'
8
+
9
+ module BetaBuilder
10
+ class Tasks < ::Rake::TaskLib
11
+ def initialize(namespace = :beta, &block)
12
+ @configuration = Configuration.new(
13
+ :configuration => "Adhoc",
14
+ :build_dir => "build",
15
+ :auto_archive => false,
16
+ :archive_path => File.expand_path("~/Library/Developer/Xcode/Archives"),
17
+ :xcodebuild_path => "xcodebuild",
18
+ :project_file_path => nil,
19
+ :workspace_path => nil,
20
+ :scheme => nil,
21
+ :app_name => nil,
22
+ :arch => nil,
23
+ :xcode4_archive_mode => false,
24
+ :skip_clean => false,
25
+ :verbose => false,
26
+ :dry_run => false
27
+ )
28
+ @namespace = namespace
29
+ yield @configuration if block_given?
30
+ define
31
+ end
32
+
33
+ def xcodebuild(*args)
34
+ # we're using tee as we still want to see our build output on screen
35
+ system("#{@configuration.xcodebuild_path} #{args.join(" ")} | tee build.output")
36
+ end
37
+
38
+ class Configuration < OpenStruct
39
+ def release_notes_text
40
+ return release_notes.call if release_notes.is_a? Proc
41
+ release_notes
42
+ end
43
+ def build_arguments
44
+ args = ""
45
+ if workspace_path
46
+ raise "A scheme is required if building from a workspace" unless scheme
47
+ args << "-workspace '#{workspace_path}' -scheme '#{scheme}' -configuration '#{configuration}'"
48
+ else
49
+ args = "-target '#{target}' -configuration '#{configuration}' -sdk iphoneos"
50
+ args << " -project #{project_file_path}" if project_file_path
51
+ end
52
+
53
+ args << " -arch \"#{arch}\"" unless arch.nil?
54
+
55
+ args
56
+ end
57
+
58
+ def archive_name
59
+ app_name || target
60
+ end
61
+
62
+ def app_file_name
63
+ raise ArgumentError, "app_name or target must be set in the BetaBuilder configuration block" if app_name.nil? && target.nil?
64
+ if app_name
65
+ "#{app_name}.app"
66
+ else
67
+ "#{target}.app"
68
+ end
69
+ end
70
+
71
+ def ipa_name
72
+ if app_name
73
+ "#{app_name}.ipa"
74
+ else
75
+ "#{target}.ipa"
76
+ end
77
+ end
78
+
79
+ def built_app_path
80
+ if build_dir == :derived
81
+ "#{derived_build_dir_from_build_output}/#{configuration}-iphoneos/#{app_file_name}"
82
+ else
83
+ "#{build_dir}/#{configuration}-iphoneos/#{app_file_name}"
84
+ end
85
+ end
86
+
87
+ def derived_build_dir_from_build_output
88
+ output = BuildOutputParser.new(File.read("build.output"))
89
+ output.build_output_dir
90
+ end
91
+
92
+ def built_app_dsym_path
93
+ "#{built_app_path}.dSYM"
94
+ end
95
+
96
+ def dist_path
97
+ File.join("pkg/dist")
98
+ end
99
+
100
+ def ipa_path
101
+ File.join(dist_path, ipa_name)
102
+ end
103
+
104
+ def deploy_using(strategy_name, &block)
105
+ if DeploymentStrategies.valid_strategy?(strategy_name.to_sym)
106
+ self.deployment_strategy = DeploymentStrategies.build(strategy_name, self)
107
+ self.deployment_strategy.configure(&block)
108
+ else
109
+ raise "Unknown deployment strategy '#{strategy_name}'."
110
+ end
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ def define
117
+ namespace(@namespace) do
118
+ desc "Build the beta release of the app"
119
+ task :build => :clean do
120
+ xcodebuild @configuration.build_arguments, "build"
121
+ end
122
+
123
+ task :clean do
124
+ unless @configuration.skip_clean
125
+ xcodebuild @configuration.build_arguments, "clean"
126
+ end
127
+ end
128
+
129
+ desc "Package the beta release as an IPA file"
130
+ task :package => :build do
131
+ if @configuration.auto_archive
132
+ Rake::Task["#{@namespace}:archive"].invoke
133
+ end
134
+
135
+ FileUtils.rm_rf('pkg') && FileUtils.mkdir_p('pkg')
136
+ FileUtils.mkdir_p("pkg/Payload")
137
+ FileUtils.mv(@configuration.built_app_path, "pkg/Payload/#{@configuration.app_file_name}")
138
+ Dir.chdir("pkg") do
139
+ system("zip -r '#{@configuration.ipa_name}' Payload")
140
+ end
141
+ FileUtils.mkdir('pkg/dist')
142
+ FileUtils.mv("pkg/#{@configuration.ipa_name}", "pkg/dist")
143
+ end
144
+
145
+ if @configuration.deployment_strategy
146
+ desc "Prepare your app for deployment"
147
+ task :prepare => :package do
148
+ @configuration.deployment_strategy.prepare
149
+ end
150
+
151
+ desc "Deploy the beta using your chosen deployment strategy"
152
+ task :deploy => :prepare do
153
+ @configuration.deployment_strategy.deploy
154
+ end
155
+
156
+ desc "Deploy the last build"
157
+ task :redeploy do
158
+ @configuration.deployment_strategy.prepare
159
+ @configuration.deployment_strategy.deploy
160
+ end
161
+ end
162
+
163
+ desc "Build and archive the app"
164
+ task :archive => :build do
165
+ puts "Archiving build..."
166
+ archive = BetaBuilder.archive(@configuration)
167
+ output_path = archive.save_to(@configuration.archive_path)
168
+ puts "Archive saved to #{output_path}."
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,101 @@
1
+ require 'uuid'
2
+ require 'fileutils'
3
+ require 'CFPropertyList'
4
+
5
+ module BetaBuilder
6
+ def self.archive(configuration)
7
+ if configuration.xcode4_archive_mode
8
+ Xcode4ArchivedBuild.new(configuration)
9
+ else
10
+ ArchivedBuild.new(configuration)
11
+ end
12
+ end
13
+
14
+ class ArchivedBuild
15
+ def initialize(configuration)
16
+ @configuration = configuration
17
+ @uuid = UUID.generate.upcase
18
+ end
19
+
20
+ def save_to(path)
21
+ archive_path = File.join(path, "#{@uuid}.apparchive")
22
+ FileUtils.mkdir(archive_path)
23
+ FileUtils.cp_r(@configuration.built_app_path, archive_path)
24
+ FileUtils.cp_r(@configuration.built_app_dsym_path, archive_path)
25
+ archive_path
26
+ end
27
+ end
28
+
29
+ class Xcode4ArchivedBuild
30
+ def initialize(configuration)
31
+ @configuration = configuration
32
+ end
33
+
34
+ def archive_file_name
35
+ "#{@configuration.archive_name} #{Time.now.strftime('%Y-%m-%d %H.%M')}.xcarchive"
36
+ end
37
+
38
+ def archive_path_within(path)
39
+ File.join(path, "#{Time.now.strftime('%Y-%m-%d')}", archive_file_name)
40
+ end
41
+
42
+ def applications_path
43
+ File.join("Products", "Applications")
44
+ end
45
+
46
+ def dsyms_path
47
+ "dSYMs"
48
+ end
49
+
50
+ def plist_info_path
51
+ File.join(@configuration.built_app_path, "Info.plist")
52
+ end
53
+
54
+ def save_to(path)
55
+ archive_path = archive_path_within(path)
56
+ FileUtils.mkdir_p(archive_path)
57
+
58
+ application_path = File.join(archive_path, applications_path)
59
+ FileUtils.mkdir_p(application_path)
60
+ FileUtils.cp_r(@configuration.built_app_path, application_path)
61
+
62
+ dsym_path = File.join(archive_path, dsyms_path)
63
+ FileUtils.mkdir_p(dsym_path)
64
+ FileUtils.cp_r(@configuration.built_app_dsym_path, dsym_path)
65
+
66
+ write_plist_to(archive_path)
67
+ archive_path
68
+ end
69
+
70
+ private
71
+
72
+ def write_plist_to(path)
73
+ version = metadata["CFBundleShortVersionString"] || metadata["CFBundleVersion"]
74
+ plist = {
75
+ "ApplicationProperties" => {
76
+ "ApplicationPath" => File.join("Applications", @configuration.app_file_name),
77
+ "CFBundleIdentifier" => metadata["CFBundleIdentifier"],
78
+ "CFBundleShortVersionString" => version,
79
+ "IconPaths" => metadata["CFBundleIconFiles"].map { |file| File.join("Applications", @configuration.app_file_name, file) }
80
+ },
81
+ "ArchiveVersion" => 1.0,
82
+ "Comment" => @configuration.release_notes_text,
83
+ "CreationDate" => Time.now,
84
+ "Name" => @configuration.archive_name,
85
+ "SchemeName" => @configuration.scheme
86
+ }
87
+ File.open(File.join(path, "Info.plist"), "w") do |io|
88
+ io.write plist.to_plist(:convert_unknown_to_string => true)
89
+ end
90
+ end
91
+
92
+ def metadata
93
+ @metadata ||= load_property_list(plist_info_path)
94
+ end
95
+
96
+ def load_property_list(path)
97
+ plist = CFPropertyList::List.new(:file => path)
98
+ CFPropertyList.native_types(plist.value)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,52 @@
1
+ module BetaBuilder
2
+ class BuildOutputParser
3
+
4
+ def initialize(output)
5
+ @output = output
6
+ end
7
+
8
+ def build_output_dir
9
+ # yes, this is truly horrible, but unless somebody else can find a better way...
10
+ found = @output.split("\n").grep(/^Validate(.*)\/Xcode\/DerivedData\/(.*)-(.*)/).first
11
+ if found && found =~ /Validate [\"]?([^\"|$]*)/
12
+ reference = $1
13
+ else
14
+ raise "Cannot parse build_dir from build output."
15
+ end
16
+ derived_data_directory = reference.split("/Build/Products/").first
17
+ "#{derived_data_directory}/Build/Products/"
18
+ end
19
+ end
20
+ end
21
+
22
+ # quick testing
23
+ if __FILE__ == $0
24
+
25
+ require 'test/unit'
26
+ class BuildOutputTest < Test::Unit::TestCase
27
+
28
+ def test_parses_output_with_unquoted_build_path
29
+ bop = BetaBuilder::BuildOutputParser.new(<<eos)
30
+ Validate /Users/johnsmith/Library/Developer/Xcode/DerivedData/Application-hegpgdbpjylesafhkxnsymrzjavl/Build/Products/Distribution-iphoneos/Application.app
31
+ cd /Users/user/app/ios
32
+ setenv PATH \"/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin:/Developer/usr/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/X11/bin\"
33
+ setenv PRODUCT_TYPE com.apple.product-type.application
34
+ /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/Validation /Users/user/Library/Developer/Xcode/DerivedData/Application-hegpgdbpjylesafhkxnsymrzjavl/Build/Products/Distribution-iphoneos/Application.app
35
+ eos
36
+ assert_equal "/Users/johnsmith/Library/Developer/Xcode/DerivedData/Application-hegpgdbpjylesafhkxnsymrzjavl/Build/Products/", bop.build_output_dir
37
+ end
38
+
39
+ def test_parses_output_with_quoted_build_path
40
+ bop = BetaBuilder::BuildOutputParser.new(<<eos)
41
+ Validate \"/Users/john smith/Library/Developer/Xcode/DerivedData/Application-hegpgdbpjylesafhkxnsymrzjavl/Build/Products/Distribution-iphoneos/Application.app\"
42
+ cd /Users/user/app/ios
43
+ setenv PATH \"/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin:/Developer/usr/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/X11/bin\"
44
+ setenv PRODUCT_TYPE com.apple.product-type.application
45
+ /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/Validation /Users/user/Library/Developer/Xcode/DerivedData/Application-hegpgdbpjylesafhkxnsymrzjavl/Build/Products/Distribution-iphoneos/Application.app
46
+ eos
47
+ assert_equal "/Users/john smith/Library/Developer/Xcode/DerivedData/Application-hegpgdbpjylesafhkxnsymrzjavl/Build/Products/", bop.build_output_dir
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,39 @@
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
+ puts "Nothing to prepare!"
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def self.strategies
32
+ {:web => Web, :testflight => TestFlight}
33
+ end
34
+ end
35
+ end
36
+
37
+ require File.dirname(__FILE__) + '/deployment_strategies/web'
38
+ require File.dirname(__FILE__) + '/deployment_strategies/testflight'
39
+
@@ -0,0 +1,88 @@
1
+ require 'rest_client'
2
+ require 'json'
3
+ require 'tmpdir'
4
+ require 'fileutils'
5
+
6
+ module BetaBuilder
7
+ module DeploymentStrategies
8
+ class TestFlight < Strategy
9
+ ENDPOINT = "https://testflightapp.com/api/builds.json"
10
+
11
+ def extended_configuration_for_strategy
12
+ proc do
13
+ def generate_release_notes(&block)
14
+ self.release_notes = block if block
15
+ end
16
+ end
17
+ end
18
+
19
+ def deploy
20
+ release_notes = get_notes
21
+ payload = {
22
+ :api_token => @configuration.api_token,
23
+ :team_token => @configuration.team_token,
24
+ :file => File.new(@configuration.ipa_path, 'rb'),
25
+ :notes => release_notes,
26
+ :distribution_lists => (@configuration.distribution_lists || []).join(","),
27
+ :notify => @configuration.notify || false,
28
+ :replace => @configuration.replace || false
29
+ }
30
+ puts "Uploading build to TestFlight..."
31
+ if @configuration.verbose
32
+ puts "ipa path: #{@configuration.ipa_path}"
33
+ puts "release notes: #{release_notes}"
34
+ end
35
+
36
+ if @configuration.dry_run
37
+ puts '** Dry Run - No action here! **'
38
+ return
39
+ end
40
+
41
+ begin
42
+ response = RestClient.post(ENDPOINT, payload, :accept => :json)
43
+ rescue => e
44
+ response = e.response
45
+ end
46
+
47
+ if (response.code == 201) || (response.code == 200)
48
+ puts "Upload complete."
49
+ else
50
+ puts "Upload failed. (#{response})"
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ def get_notes
57
+ notes = @configuration.release_notes_text
58
+ notes || get_notes_using_editor || get_notes_using_prompt
59
+ end
60
+
61
+ def get_notes_using_editor
62
+ return unless (editor = ENV["EDITOR"])
63
+
64
+ dir = Dir.mktmpdir
65
+ begin
66
+ filepath = "#{dir}/release_notes"
67
+ system("#{editor} #{filepath}")
68
+ @configuration.release_notes = File.read(filepath)
69
+ ensure
70
+ rm_rf(dir)
71
+ end
72
+ end
73
+
74
+ def get_notes_using_prompt
75
+ puts "Enter the release notes for this build (hit enter twice when done):\n"
76
+ @configuration.release_notes = gets_until_match(/\n{2}$/).strip
77
+ end
78
+
79
+ def gets_until_match(pattern, string = "")
80
+ if (string += STDIN.gets) =~ pattern
81
+ string
82
+ else
83
+ gets_until_match(pattern, string)
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,105 @@
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_file_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
+ cmd = []
88
+
89
+ cmd.push "scp"
90
+
91
+ if @configuration.remote_port
92
+ cmd.push "-P #{@configuration.remote_port}"
93
+ end
94
+
95
+ cmd.push "pkg/dist/*"
96
+ cmd.push "#{@configuration.remote_host}:#{@configuration.remote_installation_path}"
97
+
98
+ cmd = cmd.join(" ")
99
+
100
+ puts "* Running `#{cmd}`"
101
+ system(cmd)
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + '/beta_builder'
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smtlaissezfaire-betabuilder
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.4.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Luke Redpath
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-02-06 00:00:00.000000000 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: CFPropertyList
17
+ requirement: &2165417660 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 2.0.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: *2165417660
26
+ - !ruby/object:Gem::Dependency
27
+ name: uuid
28
+ requirement: &2165417160 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 2.3.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: *2165417160
37
+ - !ruby/object:Gem::Dependency
38
+ name: rest-client
39
+ requirement: &2165416660 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ version: 1.6.1
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: *2165416660
48
+ - !ruby/object:Gem::Dependency
49
+ name: json
50
+ requirement: &2165416160 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: 1.4.6
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: *2165416160
59
+ description:
60
+ email: luke@lukeredpath.co.uk
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files:
64
+ - README.md
65
+ - LICENSE
66
+ - CHANGES.md
67
+ files:
68
+ - CHANGES.md
69
+ - LICENSE
70
+ - README.md
71
+ - lib/beta_builder/archived_build.rb
72
+ - lib/beta_builder/build_output_parser.rb
73
+ - lib/beta_builder/deployment_strategies/testflight.rb
74
+ - lib/beta_builder/deployment_strategies/web.rb
75
+ - lib/beta_builder/deployment_strategies.rb
76
+ - lib/beta_builder.rb
77
+ - lib/betabuilder.rb
78
+ has_rdoc: true
79
+ homepage: http://github.com/lukeredpath/betabuilder
80
+ licenses: []
81
+ post_install_message:
82
+ rdoc_options:
83
+ - --main
84
+ - README.md
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project:
101
+ rubygems_version: 1.6.2
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: A set of Rake tasks and utilities for managing iOS ad-hoc builds.
105
+ test_files: []