yolo 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,24 @@
1
+ require 'net/smtp'
2
+
3
+ module Yolo
4
+ module Notify
5
+ module Ios
6
+ class OTAEmail < Yolo::Notify::Email
7
+
8
+ def body(opts)
9
+ file = File.open(File.dirname(__FILE__) + "/email.html", "r")
10
+ content = file.read
11
+
12
+ message = <<MESSAGE_END
13
+ To: A Test User <test@todomain.com>
14
+ MIME-Version: 1.0
15
+ Content-type: text/html
16
+ Subject: #{opts[:subject]}
17
+
18
+ #{content}
19
+ MESSAGE_END
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
data/lib/yolo/tasks.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'yolo/tasks/base_task'
2
+ require 'yolo/tasks/ios/build'
3
+ require 'yolo/tasks/ios/release'
4
+ require 'yolo/tasks/ios/ocunit'
5
+ require 'yolo/tasks/ios/calabash'
@@ -0,0 +1,22 @@
1
+ require 'xcodebuild'
2
+ require 'rake/tasklib'
3
+
4
+ module Yolo
5
+ module Tasks
6
+ class BaseTask < XcodeBuild::Tasks::BuildTask
7
+ #
8
+ # Defines available rake tasks
9
+ #
10
+ def define
11
+ namespace :yolo do
12
+ desc "Sets up yolo and moves config into place"
13
+ task :setup do
14
+ formatter = Yolo::Formatters::ProgressFormatter.new
15
+ Yolo::Config::Settings.instance.load_config
16
+ formatter.setup_complete
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ module Yolo
2
+ module Tasks
3
+ module Ios
4
+ class Build < Yolo::Tasks::BaseTask
5
+ #
6
+ # Defines rake tasks available to the Build class
7
+ #
8
+ def define
9
+ super
10
+ namespace :yolo do
11
+ desc "Builds the specified target(s)."
12
+ task :build do
13
+ xcodebuild :build
14
+ end
15
+
16
+ desc "Cleans the specified target(s)."
17
+ task :clean do
18
+ xcodebuild :clean
19
+ end
20
+
21
+ desc "Builds the specified target(s) from a clean slate."
22
+ task :cleanbuild => [:clean, :build]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,46 @@
1
+ module Yolo
2
+ module Tasks
3
+ module Ios
4
+ class Calabash < Yolo::Tasks::BaseTask
5
+
6
+ # The test report output format
7
+ attr_accessor :format
8
+ # The directory to output the test reports
9
+ attr_accessor :output_dir
10
+
11
+ #
12
+ # Initializes the class with default settings
13
+ #
14
+ def initialize
15
+ self.sdk = "iphonesimulator" unless sdk
16
+ self.format = :junit
17
+ self.output_dir = "test-reports/calabash"
18
+ super
19
+ end
20
+
21
+ #
22
+ # Defines rake tasks available to the Calabash class
23
+ #
24
+ def define
25
+ super
26
+ namespace :yolo do
27
+ namespace :calabash do
28
+ desc "Runs the specified target(s) calabash tests."
29
+ task :test do
30
+ Yolo::Tools::Ios::Calabash.run(format, output_dir)
31
+ end
32
+
33
+ desc "Cleans the specified target(s)."
34
+ task :clean do
35
+ xcodebuild :clean
36
+ end
37
+
38
+ desc "Runs the specified target(s) calabash tests from a clean slate."
39
+ task :cleantest => [:clean, :test]
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,58 @@
1
+ module Yolo
2
+ module Tasks
3
+ module Ios
4
+ class OCUnit < Yolo::Tasks::BaseTask
5
+
6
+ # the test_output type used when running tests, currently only supports
7
+ # :junit
8
+ attr_accessor :test_output
9
+
10
+ #
11
+ # Initializes the class with default settings
12
+ #
13
+ def initialize
14
+ self.sdk = "iphonesimulator" unless sdk
15
+ self.test_output = :junit
16
+ super
17
+ end
18
+
19
+ #
20
+ # Overrides the superclass build_opts_string method and appends a pipe
21
+ # if test_ouput is defined
22
+ #
23
+ # @param *additional_opts [Array] an array of additional options for the build command
24
+ #
25
+ # @return [String] the option string with additional options and test output
26
+ # pipe appended if defined
27
+ def build_opts_string(*additional_opts)
28
+ options = build_opts + additional_opts
29
+ options = options << "2>&1 | ocunit2junit" if test_output == :junit
30
+ return options.compact.join(" ")
31
+ end
32
+
33
+ #
34
+ # Defines rake tasks available to the OCUnit class
35
+ #
36
+ def define
37
+ super
38
+ namespace :yolo do
39
+ namespace :ocunit do
40
+ desc "Runs the specified target(s) OCUnit tests."
41
+ task :test do
42
+ xcodebuild :build
43
+ end
44
+
45
+ desc "Cleans the specified target(s)."
46
+ task :clean do
47
+ xcodebuild :clean
48
+ end
49
+
50
+ desc "Runs the specified target(s) OCUnit tests from a clean slate."
51
+ task :cleantest => [:clean, :test]
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,182 @@
1
+ require 'find'
2
+
3
+ module Yolo
4
+ module Tasks
5
+ module Ios
6
+ class Release < Yolo::Tasks::BaseTask
7
+
8
+ # The email addresses used when notifying
9
+ attr_accessor :mail_to
10
+ # The deployment class to use when deploying
11
+ attr_accessor :deployment
12
+
13
+ #
14
+ # Initializes the class with default settings
15
+ #
16
+ def initialize
17
+ self.sdk = "iphoneos" unless sdk
18
+ self.deployment = :OTA
19
+ @error_formatter = Yolo::Formatters::ErrorFormatter.new
20
+ @emailer = Yolo::Notify::Ios::OTAEmail.new
21
+ @xcode = Yolo::Tools::Ios::Xcode.new
22
+ @bundle_directory = Yolo::Config::Settings.instance.bundle_directory
23
+ super
24
+ end
25
+
26
+ #
27
+ # Uses Xcode to find the full path to generated app file
28
+ #
29
+ # @return [String] the path to the generated .app file
30
+ def app_path
31
+ files = []
32
+ Find.find(@xcode.build_path) do |path|
33
+ files << path if path =~ /.*#{name}-.*\/Build\/Products\/.*-iphoneos\/.*\.app$/
34
+ end
35
+ files.sort_by { |filename| File.mtime(filename)}.last # get the latest
36
+ end
37
+
38
+ #
39
+ # The name to use for folders etc taken from the scheme or target name
40
+ #
41
+ # @return [String] a name
42
+ def name
43
+ self.scheme ? self.scheme : self.target
44
+ end
45
+
46
+ #
47
+ # The path to the applications dSYM folder, the dSYM path is calculated by
48
+ # manipulating the app_path
49
+ #
50
+ # @return [String] the full path to the dSYM folder
51
+ def dsym_path
52
+ paths = app_path.split("/")
53
+ app_file = paths.last
54
+ paths.pop
55
+ path = paths.join("/")
56
+ "#{path}/#{app_file}.dSYM"
57
+ end
58
+
59
+ #
60
+ # The full path used when saving files for the build, the path is created
61
+ # using the bundle_directory, folder_name and version methods combined into
62
+ # a single path
63
+ #
64
+ # @return [String] the path to save bundle files to
65
+ def bundle_path
66
+ "#{@bundle_directory}/#{folder_name}/#{version}"
67
+ end
68
+
69
+ #
70
+ # The folder name to use for the build bundle directory, the name is created
71
+ # using the relevant scheme and build configuration
72
+ #
73
+ # @return [String] a folder name used in the bundle path
74
+ def folder_name
75
+ folder_name = name
76
+ folder_name = "#{name}-#{self.configuration}" if self.configuration
77
+ end
78
+
79
+ #
80
+ # The path to the info-plist file, the method persumes that the command is
81
+ # being called from the directory which contains the plist
82
+ #
83
+ # @return [String] the full path to the projects plist file
84
+ def info_plist_path
85
+ plist_path = ""
86
+ Find.find(Dir.pwd) do |path|
87
+ plist_path = path if path =~ /#{name}-Info.plist$/
88
+ end
89
+ plist_path
90
+ end
91
+
92
+ #
93
+ # The version string of the built application
94
+ # if the version number can not be retrieved from Xcode the current date
95
+ # time will be used
96
+ #
97
+ # @return [String] the applications version number
98
+ def version
99
+ @xcode.info_plist_path = info_plist_path
100
+ folder = ""
101
+ folder << @xcode.version_number if @xcode.version_number
102
+ folder << "-#{@xcode.build_number}" if @xcode.build_number
103
+ if folder.length == 0
104
+ time = Time.now
105
+ folder = "#{time.day}-#{time.month}-#{time.year}-#{time.hour}-#{time.min}-#{time.sec}"
106
+ end
107
+ folder
108
+ end
109
+
110
+ #
111
+ # Deploys the ipa using the defined deployment strategy, the deployment class is
112
+ # created using the deployment variable as the class name so it is crucial the correct
113
+ # class is in the project.
114
+ #
115
+ # Once deploy is succesful this method will also trigger an email notification using the
116
+ # OTAEmail class
117
+ #
118
+ # @param ipa_path [String] The full path to the IPA file to deploy
119
+ #
120
+ def deploy(ipa_path)
121
+ klass = Object.const_get("Yolo").const_get("Deployment").const_get("#{self.deployment.to_s}").new
122
+ unless klass
123
+ @error_formatter.deployment_class_error(self.deployment.to_s)
124
+ return
125
+ end
126
+
127
+ klass.deploy(ipa_path) do |url, password|
128
+ @emailer.send(
129
+ :to => self.mail_to,
130
+ :ota_url => url,
131
+ :ota_password => password,
132
+ :subject => "New #{name} build: #{version}"
133
+ )
134
+ end
135
+ end
136
+
137
+ #
138
+ # Defines the rake tasks available to the class
139
+ #
140
+ def define
141
+ super
142
+ namespace :yolo do
143
+ namespace :release do
144
+ desc "Builds and packages a release ipa of specified scheme."
145
+ task :ipa => :build do
146
+ Yolo::Tools::Ios::IPA.generate(app_path,dsym_path,bundle_path) do |ipa|
147
+ deploy(ipa) if ipa and self.deployment
148
+ end
149
+ end
150
+
151
+ desc "Builds and packages a release build for the newest git tag"
152
+ task :tag do
153
+ git = Yolo::Tools::Git.new
154
+ if git.has_new_tag(name)
155
+ Rake::Task["yolo:release:ipa"].invoke
156
+ end
157
+ end
158
+
159
+ desc "Builds and packages a release build for the newest commit"
160
+ task :commit do
161
+ git = Yolo::Tools::Git.new
162
+ if git.has_new_commit(name)
163
+ Rake::Task["yolo:release:ipa"].invoke
164
+ end
165
+ end
166
+
167
+ desc "Generates a release notes file"
168
+ task :notes do
169
+ Yolo::Tools::Ios::ReleaseNotes.generate(info_plist_path)
170
+ end
171
+ end
172
+
173
+ desc "Builds the specified scheme."
174
+ task :build do
175
+ xcodebuild :build
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+ end
data/lib/yolo/tools.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'yolo/tools/git'
2
+ require 'yolo/tools/ios/calabash'
3
+ require 'yolo/tools/ios/xcode'
4
+ require 'yolo/tools/ios/ipa'
5
+ require 'yolo/tools/ios/release_notes'
@@ -0,0 +1,190 @@
1
+ require 'yaml'
2
+
3
+ module Yolo
4
+ module Tools
5
+ class Git
6
+
7
+ # A unique name used to identify the project and its history
8
+ attr_accessor :project_name
9
+
10
+ #
11
+ # Creates the class with default variables, sets the path to the history
12
+ # yml file and loads it.
13
+ #
14
+ def initialize
15
+ @yaml_path = File.dirname(__FILE__) + "/../history/history.yml"
16
+ @yaml = YAML::load_file @yaml_path
17
+ @formatter = Yolo::Formatters::ProgressFormatter.new
18
+ end
19
+
20
+ #
21
+ # Checks against the yaml history to see if a new commit is present to build
22
+ # @param name [String] the project name, used as a unique id in the yaml history
23
+ #
24
+ # @return [BOOL] returns if there is a new commit to build
25
+ def has_new_commit(name)
26
+ self.project_name = name unless name.nil?
27
+ commit = latest_commit
28
+ if yaml_commit == commit
29
+ @formatter.no_new_commit
30
+ @commit = nil
31
+ false
32
+ else
33
+ @formatter.new_commit(commit)
34
+ update_commit(commit)
35
+ @commit = commit
36
+ true
37
+ end
38
+ end
39
+
40
+ #
41
+ # Checks against the yaml history to see if a new tag is present to build
42
+ # @param name [String] the project name, used as a unique id in the yaml history
43
+ #
44
+ # @return [BOOL] returns if there is a new tag to build
45
+ def has_new_tag(name)
46
+ self.project_name = name unless name.nil?
47
+ tag = latest_tag
48
+ if yaml_tag == tag
49
+ @formatter.no_new_tag
50
+ @tag = nil
51
+ false
52
+ else
53
+ @formatter.new_tag(tag)
54
+ update_tag(tag)
55
+ @tag = tag
56
+ true
57
+ end
58
+ end
59
+
60
+ #
61
+ # The latest git tag
62
+ #
63
+ # @return [String] The latest git tag for the project
64
+ def tag
65
+ @tag
66
+ end
67
+
68
+ #
69
+ # The latest commit hash
70
+ #
71
+ # @return [String] The latest git commit hash for the project
72
+ def commit
73
+ @commit
74
+ end
75
+
76
+ private
77
+
78
+
79
+ #
80
+ # Updates the tag history
81
+ # @param new_tag [String] The new tag to store as the latest
82
+ #
83
+ def update_tag(new_tag)
84
+ if @yaml[project_name]
85
+ @yaml[project_name]["tag"] = new_tag
86
+ else
87
+ @yaml[project_name] = {"tag" => new_tag, "commit" => ""}
88
+ end
89
+ save_yaml
90
+ end
91
+
92
+ #
93
+ # Updates the commit history
94
+ # @param new_commit [String] The new commit hash to store as the latest
95
+ #
96
+ def update_commit(new_commit)
97
+ if @yaml[project_name]
98
+ @yaml[project_name]["commit"] = new_commit
99
+ else
100
+ @yaml[project_name] = {"tag" => "", "commit" => new_commit}
101
+ end
102
+ save_yaml
103
+ end
104
+
105
+ #
106
+ # Writes the yaml hash to the history file saving the latest commit and tag
107
+ #
108
+ def save_yaml
109
+ File.open(@yaml_path, 'w') {|f|
110
+ f.write(@yaml.to_yaml)
111
+ }
112
+ end
113
+
114
+ #
115
+ # Uses git log and some regex to find the latest tag on the current branch, returns
116
+ # empty if no tag is found
117
+ #
118
+ # @return [String] The tag
119
+ def latest_tag
120
+ match = log.scan(/tag:\sv?[0-9]*\.[0-9]*[\.[0-9]*]*[[a-zA-Z]*]?/).first
121
+ if match.nil?
122
+ ""
123
+ else
124
+ match
125
+ end
126
+ end
127
+
128
+
129
+ #
130
+ # Uses git log and some regex to find the latest commit hash on the current branch, returns
131
+ # empty if no tag is found
132
+ #
133
+ # @return [String] The commit hash
134
+ def latest_commit
135
+ match = log.scan(/\b[0-9a-f]{5,40}\b/).first
136
+ if match.nil?
137
+ ""
138
+ else
139
+ match
140
+ end
141
+ end
142
+
143
+ #
144
+ # The last commit that was checked, loaded from the yaml file
145
+ #
146
+ # @return [String] The last commit hash from the project history
147
+ def yaml_commit
148
+ if @yaml[project_name]
149
+ @yaml[project_name]["commit"]
150
+ else
151
+ ""
152
+ end
153
+ end
154
+
155
+ #
156
+ # The last tag that was checked, loaded from the yaml file
157
+ #
158
+ # @return [String] The last tag from the project history
159
+ def yaml_tag
160
+ if @yaml[project_name]
161
+ @yaml[project_name]["tag"]
162
+ else
163
+ ""
164
+ end
165
+ end
166
+
167
+ #
168
+ # Executes a git log command on the current branch
169
+ #
170
+ # @return [String] git log's output
171
+ def log
172
+ `git log #{current_branch} --decorate=short -n 1 --pretty=oneline`
173
+ end
174
+
175
+ #
176
+ # Finds the current branch using some regex and git branch
177
+ #
178
+ # @return [String] The current branch name
179
+ def current_branch
180
+ branch = `git branch`
181
+ branchs = branch.split("\n")
182
+ current_branch = ""
183
+ branchs.each do |b|
184
+ current_branch = b if b.count("*") == 1
185
+ end
186
+ current_branch.gsub("* ", "")
187
+ end
188
+ end
189
+ end
190
+ end