yolo 1.0.0.pre
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.
- data/README.md +4 -0
- data/Rakefile +32 -0
- data/lib/yolo.rb +13 -0
- data/lib/yolo/config.rb +1 -0
- data/lib/yolo/config/config.yml +16 -0
- data/lib/yolo/config/settings.rb +89 -0
- data/lib/yolo/deployment.rb +2 -0
- data/lib/yolo/deployment/base_deployer.rb +14 -0
- data/lib/yolo/deployment/ota.rb +74 -0
- data/lib/yolo/formatters.rb +2 -0
- data/lib/yolo/formatters/error_formatter.rb +33 -0
- data/lib/yolo/formatters/progress_formatter.rb +96 -0
- data/lib/yolo/history/history.yml +3 -0
- data/lib/yolo/notify.rb +2 -0
- data/lib/yolo/notify/email.rb +64 -0
- data/lib/yolo/notify/ios/email.html +567 -0
- data/lib/yolo/notify/ios/ota_email.rb +24 -0
- data/lib/yolo/tasks.rb +5 -0
- data/lib/yolo/tasks/base_task.rb +22 -0
- data/lib/yolo/tasks/ios/build.rb +28 -0
- data/lib/yolo/tasks/ios/calabash.rb +46 -0
- data/lib/yolo/tasks/ios/ocunit.rb +58 -0
- data/lib/yolo/tasks/ios/release.rb +182 -0
- data/lib/yolo/tools.rb +5 -0
- data/lib/yolo/tools/git.rb +190 -0
- data/lib/yolo/tools/ios/calabash.rb +26 -0
- data/lib/yolo/tools/ios/ipa.rb +31 -0
- data/lib/yolo/tools/ios/release_notes.rb +34 -0
- data/lib/yolo/tools/ios/xcode.rb +51 -0
- metadata +138 -0
@@ -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,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,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
|