ot-ios-builder 0.7.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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["CFBundleIcons"]["CFBundlePrimaryIcon"]["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,56 @@
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
+
20
+ def failed?
21
+ @output.split("\n").any? {|line| line.include? "** BUILD FAILED **"}
22
+ end
23
+ end
24
+ end
25
+
26
+ # quick testing
27
+ if __FILE__ == $0
28
+
29
+ require 'test/unit'
30
+ class BuildOutputTest < Test::Unit::TestCase
31
+
32
+ def test_parses_output_with_unquoted_build_path
33
+ bop = BetaBuilder::BuildOutputParser.new(<<eos)
34
+ Validate /Users/johnsmith/Library/Developer/Xcode/DerivedData/Application-hegpgdbpjylesafhkxnsymrzjavl/Build/Products/Distribution-iphoneos/Application.app
35
+ cd /Users/user/app/ios
36
+ 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\"
37
+ setenv PRODUCT_TYPE com.apple.product-type.application
38
+ /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/Validation /Users/user/Library/Developer/Xcode/DerivedData/Application-hegpgdbpjylesafhkxnsymrzjavl/Build/Products/Distribution-iphoneos/Application.app
39
+ eos
40
+ assert_equal "/Users/johnsmith/Library/Developer/Xcode/DerivedData/Application-hegpgdbpjylesafhkxnsymrzjavl/Build/Products/", bop.build_output_dir
41
+ end
42
+
43
+ def test_parses_output_with_quoted_build_path
44
+ bop = BetaBuilder::BuildOutputParser.new(<<eos)
45
+ Validate \"/Users/john smith/Library/Developer/Xcode/DerivedData/Application-hegpgdbpjylesafhkxnsymrzjavl/Build/Products/Distribution-iphoneos/Application.app\"
46
+ cd /Users/user/app/ios
47
+ 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\"
48
+ setenv PRODUCT_TYPE com.apple.product-type.application
49
+ /Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/Validation /Users/user/Library/Developer/Xcode/DerivedData/Application-hegpgdbpjylesafhkxnsymrzjavl/Build/Products/Distribution-iphoneos/Application.app
50
+ eos
51
+ assert_equal "/Users/john smith/Library/Developer/Xcode/DerivedData/Application-hegpgdbpjylesafhkxnsymrzjavl/Build/Products/", bop.build_output_dir
52
+ end
53
+
54
+ end
55
+
56
+ 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!" if @configuration.verbose
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,92 @@
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
+ include Rake::DSL
10
+ include FileUtils
11
+ ENDPOINT = "https://testflightapp.com/api/builds.json"
12
+
13
+ def extended_configuration_for_strategy
14
+ proc do
15
+ def generate_release_notes(&block)
16
+ self.release_notes = block if block
17
+ end
18
+ end
19
+ end
20
+
21
+ def deploy
22
+ release_notes = get_notes
23
+ payload = {
24
+ :api_token => @configuration.api_token,
25
+ :team_token => @configuration.team_token,
26
+ :file => File.new(@configuration.ipa_path, 'rb'),
27
+ :notes => release_notes,
28
+ :distribution_lists => (@configuration.distribution_lists || []).join(","),
29
+ :notify => @configuration.notify || false,
30
+ :replace => @configuration.replace || false
31
+ }
32
+ if @configuration.verbose
33
+ puts "ipa path: #{@configuration.ipa_path}"
34
+ puts "release notes: #{release_notes}"
35
+ end
36
+
37
+ if @configuration.dry_run
38
+ puts '** Dry Run - No action here! **'
39
+ return
40
+ end
41
+
42
+ print "Uploading build to TestFlight..."
43
+
44
+ begin
45
+ response = RestClient.post(ENDPOINT, payload, :accept => :json)
46
+ rescue => e
47
+ response = e.response
48
+ end
49
+
50
+ if (response.code == 201) || (response.code == 200)
51
+ puts "Done."
52
+ else
53
+ puts "Failed."
54
+ puts "#{response}"
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def get_notes
61
+ notes = @configuration.release_notes_text
62
+ notes || get_notes_using_editor || get_notes_using_prompt
63
+ end
64
+
65
+ def get_notes_using_editor
66
+ return unless (editor = ENV["EDITOR"])
67
+
68
+ dir = Dir.mktmpdir
69
+ begin
70
+ filepath = "#{dir}/release_notes"
71
+ system("#{editor} #{filepath}")
72
+ @configuration.release_notes = File.read(filepath)
73
+ ensure
74
+ rm_rf(dir)
75
+ end
76
+ end
77
+
78
+ def get_notes_using_prompt
79
+ puts "Enter the release notes for this build (hit enter twice when done):\n"
80
+ @configuration.release_notes = gets_until_match(/\n{2}$/).strip
81
+ end
82
+
83
+ def gets_until_match(pattern, string = "")
84
+ if (string += STDIN.gets) =~ pattern
85
+ string
86
+ else
87
+ gets_until_match(pattern, string)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,104 @@
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, ipa_name)
8
+ end
9
+
10
+ def manifest_url
11
+ File.join(deploy_to, "manifest.plist")
12
+ end
13
+
14
+ def remote_installation_path
15
+ File.join(remote_directory)
16
+ end
17
+ end
18
+ end
19
+
20
+ def prepare
21
+ plist = CFPropertyList::List.new(:file => "#{@configuration.built_app_path}/Info.plist")
22
+ plist_data = CFPropertyList.native_types(plist.value)
23
+ File.open("pkg/dist/manifest.plist", "w") do |io|
24
+ io << %{<?xml version="1.0" encoding="UTF-8"?>
25
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
26
+ <plist version="1.0">
27
+ <dict>
28
+ <key>items</key>
29
+ <array>
30
+ <dict>
31
+ <key>assets</key>
32
+ <array>
33
+ <dict>
34
+ <key>kind</key>
35
+ <string>software-package</string>
36
+ <key>url</key>
37
+ <string>#{@configuration.deployment_url}</string>
38
+ </dict>
39
+ </array>
40
+ <key>metadata</key>
41
+ <dict>
42
+ <key>bundle-identifier</key>
43
+ <string>#{plist_data['CFBundleIdentifier']}</string>
44
+ <key>bundle-version</key>
45
+ <string>#{plist_data['CFBundleVersion']}</string>
46
+ <key>kind</key>
47
+ <string>software</string>
48
+ <key>title</key>
49
+ <string>#{plist_data['CFBundleDisplayName']}</string>
50
+ </dict>
51
+ </dict>
52
+ </array>
53
+ </dict>
54
+ </plist>
55
+ }
56
+ end
57
+ File.open("pkg/dist/index.html", "w") do |io|
58
+ io << %{<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
59
+ <html xmlns="http://www.w3.org/1999/xhtml">
60
+ <head>
61
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
62
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
63
+ <title>Beta Download</title>
64
+ <style type="text/css">
65
+ body {background:#fff;margin:0;padding:0;font-family:arial,helvetica,sans-serif;text-align:center;padding:10px;color:#333;font-size:16px;}
66
+ #container {width:300px;margin:0 auto;}
67
+ h1 {margin:0;padding:0;font-size:14px;}
68
+ p {font-size:13px;}
69
+ .link {background:#ecf5ff;border-top:1px solid #fff;border:1px solid #dfebf8;margin-top:.5em;padding:.3em;}
70
+ .link a {text-decoration:none;font-size:15px;display:block;color:#069;}
71
+ </style>
72
+ </head>
73
+ <body>
74
+ <div id="container">
75
+ <div class="link"><a href="itms-services://?action=download-manifest&url=#{@configuration.manifest_url}">Tap Here to Install<br />#{@configuration.target} #{plist_data['CFBundleVersion']}<br />On Your Device</a></div>
76
+ <p><strong>Link didn't work?</strong><br />
77
+ Make sure you're visiting this page on your device, not your computer.</p>
78
+ </div>
79
+ </body>
80
+ </html>
81
+ }
82
+ end
83
+ end
84
+
85
+ def deploy
86
+ cmd = []
87
+
88
+ cmd.push "scp"
89
+
90
+ if @configuration.remote_port
91
+ cmd.push "-P #{@configuration.remote_port}"
92
+ end
93
+
94
+ cmd.push "pkg/dist/*"
95
+ cmd.push "#{@configuration.remote_host}:#{@configuration.remote_installation_path}"
96
+
97
+ cmd = cmd.join(" ")
98
+
99
+ puts "* Running `#{cmd}`"
100
+ system(cmd)
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,34 @@
1
+ module BetaBuilder
2
+ module ReleaseStrategies
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 ReleaseStrategy
12
+ def initialize(configuration)
13
+ @configuration = configuration
14
+ end
15
+
16
+ def configure(&block)
17
+ yield self
18
+ end
19
+ end
20
+
21
+ def prepare
22
+ puts "Nothing to prepare!" if @configuration.verbose
23
+ end
24
+
25
+ private
26
+
27
+ def self.strategies
28
+ {:git => Git}
29
+ end
30
+ end
31
+ end
32
+
33
+ require File.dirname(__FILE__) + '/release_strategies/git'
34
+
@@ -0,0 +1,116 @@
1
+ module BetaBuilder
2
+ module ReleaseStrategies
3
+ class Git < ReleaseStrategy
4
+ attr_accessor :branch, :origin, :tag_name
5
+
6
+ def prepare
7
+
8
+ if @origin == nil then
9
+ @origin = "origin"
10
+ end
11
+
12
+ if @branch == nil then
13
+ @branch = "master"
14
+ end
15
+
16
+ if @tag_name == nil then
17
+ @tag_name = "v#{@configuration.build_number}"
18
+ end
19
+ end
20
+
21
+ def tag_current_version
22
+ puts "Relasing with Git"
23
+ print "Tagging version #{@tag_name}"
24
+ cmd = []
25
+
26
+ #first, tag
27
+ cmd << "git"
28
+ cmd << "tag"
29
+ # -f sounds brutal to start with, so let's give it a try without
30
+ # cmd << "-f"
31
+ cmd << @tag_name
32
+
33
+ cmd << "2>&1 %s git.output" % (@configuration.verbose ? '| tee' : '>')
34
+ system(cmd.join " ")
35
+ puts
36
+ puts "Done"
37
+
38
+ # then push tags to the remote server
39
+ print "Pushing tag to #{@origin} on branch #{@branch}"
40
+ cmd = []
41
+
42
+ cmd << "git"
43
+ cmd << "push"
44
+ cmd << "--tags"
45
+ cmd << @origin
46
+ cmd << @branch
47
+ cmd << "2>&1 %s git.output" % (@configuration.verbose ? '| tee' : '>')
48
+ system(cmd.join " ")
49
+
50
+ puts
51
+ puts "Done"
52
+
53
+ end
54
+
55
+ def prepare_for_next_pod_release
56
+ build_number = @configuration.build_number
57
+ raise "build number cannot be empty on release" unless (build_number != nil) && (!build_number.empty?)
58
+
59
+ print "Committing #{@configuration.app_info_plist} and #{@configuration.spec_file} with version #{build_number}"
60
+
61
+ stage_files [@configuration.app_info_plist, @configuration.spec_file]
62
+ commit_and_push_with_message "Preparing for next pod release..."
63
+
64
+ puts "Done"
65
+ end
66
+
67
+ def prepare_for_next_release
68
+ build_number = @configuration.build_number
69
+ raise "build number cannot be empty on release" unless (build_number != nil) && (!build_number.empty?)
70
+
71
+ print "Committing #{@configuration.app_info_plist} with version #{build_number}"
72
+
73
+ stage_files [@configuration.app_info_plist]
74
+ commit_and_push_with_message "Preparing for next release..."
75
+
76
+ puts "Done"
77
+ end
78
+
79
+ def stage_files files
80
+ cmd = []
81
+
82
+ cmd << "git"
83
+ cmd << "add"
84
+ files.each do |value|
85
+ cmd << value
86
+ end
87
+ system(cmd.join " ")
88
+ end
89
+
90
+ def commit_and_push_with_message message
91
+ # then commit it
92
+ cmd = []
93
+ cmd << "git"
94
+ cmd << "commit"
95
+ cmd << "-m"
96
+ cmd << "'#{message}'"
97
+ cmd << "2>&1 %s git.output" % (@configuration.verbose ? '| tee' : '>')
98
+ system(cmd.join " ")
99
+ puts
100
+ puts "Done"
101
+
102
+ # now, push the updated plist
103
+ print "Pushing update to #{@origin}/#{@branch}"
104
+ cmd = []
105
+ cmd << "git"
106
+ cmd << "push"
107
+ cmd << @origin
108
+ cmd << @branch
109
+ cmd << "2>&1 %s git.output" % (@configuration.verbose ? '| tee' : '>')
110
+
111
+ system(cmd.join " ")
112
+ puts
113
+ end
114
+ end
115
+ end
116
+ end