fastlane 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +21 -0
- data/README.md +418 -0
- data/bin/fastlane +63 -0
- data/lib/assets/AppfileTemplate +2 -0
- data/lib/assets/FastfileTemplate +56 -0
- data/lib/assets/custom_action_template.rb +17 -0
- data/lib/fastlane.rb +28 -0
- data/lib/fastlane/actions/README +3 -0
- data/lib/fastlane/actions/actions_helper.rb +113 -0
- data/lib/fastlane/actions/deliver.rb +28 -0
- data/lib/fastlane/actions/frameit.rb +23 -0
- data/lib/fastlane/actions/hockey.rb +47 -0
- data/lib/fastlane/actions/increment_build_number.rb +40 -0
- data/lib/fastlane/actions/install_cocapods.rb +9 -0
- data/lib/fastlane/actions/say.rb +10 -0
- data/lib/fastlane/actions/sigh.rb +31 -0
- data/lib/fastlane/actions/slack.rb +56 -0
- data/lib/fastlane/actions/snapshot.rb +30 -0
- data/lib/fastlane/actions/testmunk.rb +42 -0
- data/lib/fastlane/actions/xctool.rb +10 -0
- data/lib/fastlane/dependency_checker.rb +18 -0
- data/lib/fastlane/fast_file.rb +89 -0
- data/lib/fastlane/fastlane_folder.rb +24 -0
- data/lib/fastlane/helper.rb +66 -0
- data/lib/fastlane/junit_generator.rb +32 -0
- data/lib/fastlane/lane_manager.rb +39 -0
- data/lib/fastlane/new_action.rb +39 -0
- data/lib/fastlane/runner.rb +58 -0
- data/lib/fastlane/setup.rb +157 -0
- data/lib/fastlane/update_checker.rb +44 -0
- data/lib/fastlane/version.rb +3 -0
- metadata +121 -33
@@ -0,0 +1,31 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Actions
|
3
|
+
module SharedValues
|
4
|
+
SIGH_PROFILE_PATH = :SIGH_PROFILE_PATH
|
5
|
+
end
|
6
|
+
|
7
|
+
class SighAction
|
8
|
+
def self.run(params)
|
9
|
+
require 'sigh'
|
10
|
+
require 'credentials_manager/appfile_config'
|
11
|
+
|
12
|
+
type = Sigh::DeveloperCenter::APPSTORE
|
13
|
+
type = Sigh::DeveloperCenter::ADHOC if params.first == :adhoc
|
14
|
+
type = Sigh::DeveloperCenter::DEVELOPMENT if params.first == :development
|
15
|
+
|
16
|
+
return type if Helper.is_test?
|
17
|
+
|
18
|
+
app = CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier)
|
19
|
+
raise "No app_identifier definied in `./fastlane/Appfile`".red unless app
|
20
|
+
|
21
|
+
path = Sigh::DeveloperCenter.new.run(app, type)
|
22
|
+
output_path = File.expand_path(File.join('.', File.basename(path)))
|
23
|
+
FileUtils.mv(path, output_path)
|
24
|
+
Helper.log.info "Exported provisioning profile to '#{output_path}'".green
|
25
|
+
Actions.sh "open '#{output_path}'"
|
26
|
+
|
27
|
+
Actions.lane_context[SharedValues::SIGH_PROFILE_PATH] = output_path # absolute URL
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Actions
|
3
|
+
module SharedValues
|
4
|
+
|
5
|
+
end
|
6
|
+
|
7
|
+
class SlackAction
|
8
|
+
def self.run(params)
|
9
|
+
options = { message: '',
|
10
|
+
success: true,
|
11
|
+
channel: nil
|
12
|
+
}.merge(params.first || {})
|
13
|
+
|
14
|
+
require 'slack-notifier'
|
15
|
+
|
16
|
+
color = (options[:success] ? 'good' : 'danger')
|
17
|
+
options[:message] = Slack::Notifier::LinkFormatter.format(options[:message])
|
18
|
+
|
19
|
+
url = ENV["SLACK_URL"]
|
20
|
+
unless url
|
21
|
+
Helper.log.fatal "Please add 'ENV[\"SLACK_URL\"] = \"https://hooks.slack.com/services/...\"' to your Fastfile's `before_all` section.".red
|
22
|
+
raise "No SLACK_URL given.".red
|
23
|
+
end
|
24
|
+
|
25
|
+
notifier = Slack::Notifier.new url
|
26
|
+
|
27
|
+
notifier.username = 'fastlane'
|
28
|
+
notifier.channel = "##{options[:channel]}" if options[:channel].to_s.length > 0
|
29
|
+
|
30
|
+
test_result = {
|
31
|
+
fallback: options[:message],
|
32
|
+
text: options[:message],
|
33
|
+
color: color,
|
34
|
+
fields: [
|
35
|
+
{
|
36
|
+
title: "Lane",
|
37
|
+
value: Actions.lane_context[Actions::SharedValues::LANE_NAME],
|
38
|
+
short: true
|
39
|
+
},
|
40
|
+
{
|
41
|
+
title: "Test Result",
|
42
|
+
value: (options[:success] ? "Success" : "Error"),
|
43
|
+
short: true
|
44
|
+
}
|
45
|
+
]
|
46
|
+
}
|
47
|
+
|
48
|
+
notifier.ping "",
|
49
|
+
icon_url: 'https://s3-eu-west-1.amazonaws.com/fastlane.tools/fastlane.png',
|
50
|
+
attachments: [test_result]
|
51
|
+
|
52
|
+
Helper.log.info "Successfully sent Slack notification"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Actions
|
3
|
+
module SharedValues
|
4
|
+
SNAPSHOT_SCREENSHOTS_PATH = :SNAPSHOT_SCREENSHOTS_PATH
|
5
|
+
end
|
6
|
+
|
7
|
+
class SnapshotAction
|
8
|
+
def self.run(params)
|
9
|
+
require 'snapshot'
|
10
|
+
|
11
|
+
clean = true
|
12
|
+
clean = false if params.first == :noclean
|
13
|
+
|
14
|
+
if Helper.is_test?
|
15
|
+
Actions.lane_context[SharedValues::SNAPSHOT_SCREENSHOTS_PATH] = Dir.pwd
|
16
|
+
return clean
|
17
|
+
end
|
18
|
+
|
19
|
+
Dir.chdir(FastlaneFolder.path) do
|
20
|
+
Snapshot::SnapshotConfig.shared_instance
|
21
|
+
Snapshot::Runner.new.work(clean: clean)
|
22
|
+
|
23
|
+
results_path = Snapshot::SnapshotConfig.shared_instance.screenshots_path
|
24
|
+
|
25
|
+
Actions.lane_context[SharedValues::SNAPSHOT_SCREENSHOTS_PATH] = File.expand_path(results_path) # absolute URL
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# How it works:
|
2
|
+
# You can run your functional calabash testcases on real ios hardware. Follow the steps to set up your free account on testmunk.com or see below. After tests are executed you will get an email with test results. An API extension to see test results directly in Jenkins is in the works.
|
3
|
+
|
4
|
+
|
5
|
+
# Setup
|
6
|
+
# 1) Create a free account on testmunk.com
|
7
|
+
# 2) Create an own project under your account (top right) after you are logged in. You will need to use this project name within your REST API upload path.
|
8
|
+
# 3) Upload testcases (features in calabash) over the testmunk REST API (http://docs.testmunk.com/en/latest/rest.html#upload-testcases).
|
9
|
+
|
10
|
+
|
11
|
+
module Fastlane
|
12
|
+
module Actions
|
13
|
+
module SharedValues
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
class TestmunkAction
|
18
|
+
def self.run(params)
|
19
|
+
|
20
|
+
raise "Please pass your Testmunk email address using `ENV['TESTMUNK_EMAIL'] = 'value'`" unless ENV['TESTMUNK_EMAIL']
|
21
|
+
raise "Please pass your Testmunk API Key using `ENV['TESTMUNK_API'] = 'value'`" unless ENV['TESTMUNK_API']
|
22
|
+
raise "Please pass your Testmunk app name using `ENV['TESTMUNK_APP'] = 'value'`" unless ENV['TESTMUNK_APP']
|
23
|
+
|
24
|
+
ipa_path = ENV['TESTMUNK_IPA'] || ENV[Actions::SharedValues::DELIVER_IPA_PATH.to_s]
|
25
|
+
raise "Please pass a path to your ipa file using `ENV['TESTMUNK_IPA'] = 'value'`" unless ipa_path
|
26
|
+
|
27
|
+
Helper.log.info "Testmunk: Uploading the .ipa and starting your tests".green
|
28
|
+
|
29
|
+
response = system("#{"curl -H 'Accept: application/vnd.testmunk.v1+json'" +
|
30
|
+
" -F 'file=@#{ipa_path}' -F 'autoStart=true'" +
|
31
|
+
" -F 'email=#{ENV['TESTMUNK_EMAIL']}'" +
|
32
|
+
" https://#{ENV['TESTMUNK_API']}@api.testmunk.com/apps/#{ENV['TESTMUNK_APP']}/testruns"}")
|
33
|
+
|
34
|
+
if response
|
35
|
+
Helper.log.info "Your tests are being executed right now. Please wait for the mail with results and decide if you want to continue.".green
|
36
|
+
else
|
37
|
+
raise "Something went wrong while uploading your app to Testmunk".red
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Fastlane
|
2
|
+
class DependencyChecker
|
3
|
+
def self.check_dependencies
|
4
|
+
self.check_xcode_select unless Helper.is_test?
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.check_xcode_select
|
8
|
+
unless `xcode-select -v`.include?"xcode-select version "
|
9
|
+
Helper.log.fatal '#############################################################'
|
10
|
+
Helper.log.fatal "# You have to install the Xcode commdand line tools to use fastlane"
|
11
|
+
Helper.log.fatal "# Install the latest version of Xcode from the AppStore"
|
12
|
+
Helper.log.fatal "# Run xcode-select --install to install the developer tools"
|
13
|
+
Helper.log.fatal '#############################################################'
|
14
|
+
raise "Run 'xcode-select --install' and start fastlane again"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Fastlane
|
2
|
+
class FastFile
|
3
|
+
attr_accessor :runner
|
4
|
+
|
5
|
+
# @return The runner which can be executed to trigger the given actions
|
6
|
+
def initialize(path = nil)
|
7
|
+
if (path || '').length > 0
|
8
|
+
raise "Could not find Fastfile at path '#{path}'".red unless File.exists?path
|
9
|
+
@path = path
|
10
|
+
content = File.read(path)
|
11
|
+
|
12
|
+
parse(content)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse(data)
|
17
|
+
@runner = Runner.new
|
18
|
+
|
19
|
+
Dir.chdir(Fastlane::FastlaneFolder.path || Dir.pwd) do # context: fastlane subfolder
|
20
|
+
eval(data) # this is okay in this case
|
21
|
+
end
|
22
|
+
|
23
|
+
return self
|
24
|
+
end
|
25
|
+
|
26
|
+
def lane(key, &block)
|
27
|
+
@runner.set_block(key, block)
|
28
|
+
end
|
29
|
+
|
30
|
+
def before_all(&block)
|
31
|
+
@runner.set_before_all(block)
|
32
|
+
end
|
33
|
+
|
34
|
+
def after_all(&block)
|
35
|
+
@runner.set_after_all(block)
|
36
|
+
end
|
37
|
+
|
38
|
+
def error(&block)
|
39
|
+
@runner.set_error(block)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Speak out loud
|
43
|
+
def say(value)
|
44
|
+
# Overwrite this, since there is already a 'say' method defined in the Ruby standard library
|
45
|
+
value ||= yield
|
46
|
+
Actions.execute_action('say') do
|
47
|
+
Fastlane::Actions::SayAction.run([value])
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def actions_path(path)
|
52
|
+
raise "Path '#{path}' not found!".red unless File.directory?path
|
53
|
+
|
54
|
+
Actions.load_external_actions(path)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Execute shell command
|
58
|
+
def sh(command)
|
59
|
+
Actions.execute_action(command) do
|
60
|
+
Actions.sh_no_action(command)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def method_missing(method_sym, *arguments, &block)
|
65
|
+
# First, check if there is a predefined method in the actions folder
|
66
|
+
|
67
|
+
class_name = method_sym.to_s.classify + "Action"
|
68
|
+
class_ref = nil
|
69
|
+
begin
|
70
|
+
class_ref = Fastlane::Actions.const_get(class_name)
|
71
|
+
rescue NameError => ex
|
72
|
+
# Action not found
|
73
|
+
raise "Could not find method '#{method_sym}'. Check out the README for more details: https://github.com/KrauseFx/fastlane".red
|
74
|
+
end
|
75
|
+
|
76
|
+
if class_ref and class_ref.respond_to?(:run)
|
77
|
+
Helper.log.info "Step: #{method_sym.to_s}".green
|
78
|
+
|
79
|
+
Dir.chdir("..") do # go up from the fastlane folder, to the project folder
|
80
|
+
Actions.execute_action(method_sym) do
|
81
|
+
class_ref.run(arguments)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
else
|
85
|
+
raise "Action '#{method_sym}' of class '#{class_name}' was found, but has no `run` method.".red
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Fastlane
|
2
|
+
class FastlaneFolder
|
3
|
+
FOLDER_NAME = 'fastlane'
|
4
|
+
|
5
|
+
def self.path
|
6
|
+
return "./#{FOLDER_NAME}/" if File.directory?"./#{FOLDER_NAME}/"
|
7
|
+
return "./.#{FOLDER_NAME}/" if File.directory?"./.#{FOLDER_NAME}/" # hidden folder
|
8
|
+
return "./" if File.basename(Dir.getwd) == FOLDER_NAME and File.exists?"Fastfile" # inside the folder
|
9
|
+
return "./" if File.basename(Dir.getwd) == FOLDER_NAME and File.exists?"Fastfile" # inside the folder and hidden
|
10
|
+
return nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.setup?
|
14
|
+
return false unless self.path
|
15
|
+
return File.exists?self.path
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.create_folder!
|
19
|
+
path = "./#{FOLDER_NAME}"
|
20
|
+
FileUtils.mkdir_p path
|
21
|
+
Helper.log.info "Created new folder '#{path}'.".green
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
|
4
|
+
class String
|
5
|
+
def classify
|
6
|
+
self.split('_').collect!{ |w| w.capitalize }.join
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
module Fastlane
|
13
|
+
module Helper
|
14
|
+
|
15
|
+
# Logging happens using this method
|
16
|
+
def self.log
|
17
|
+
if is_test?
|
18
|
+
@@log ||= Logger.new(nil) # don't show any logs when running tests
|
19
|
+
else
|
20
|
+
@@log ||= Logger.new(STDOUT)
|
21
|
+
end
|
22
|
+
|
23
|
+
@@log.formatter = proc do |severity, datetime, progname, msg|
|
24
|
+
string = "#{severity} [#{datetime.strftime('%Y-%m-%d %H:%M:%S.%2N')}]: "
|
25
|
+
second = "#{msg}\n"
|
26
|
+
|
27
|
+
if severity == "DEBUG"
|
28
|
+
string = string.magenta
|
29
|
+
elsif severity == "INFO"
|
30
|
+
string = string.white
|
31
|
+
elsif severity == "WARN"
|
32
|
+
string = string.yellow
|
33
|
+
elsif severity == "ERROR"
|
34
|
+
string = string.red
|
35
|
+
elsif severity == "FATAL"
|
36
|
+
string = string.red.bold
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
[string, second].join("")
|
41
|
+
end
|
42
|
+
|
43
|
+
@@log
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return true if the currently running program is a unit test
|
47
|
+
def self.is_test?
|
48
|
+
defined?SpecHelper
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return the full path to the Xcode developer tools of the currently
|
52
|
+
# running system
|
53
|
+
def self.xcode_path
|
54
|
+
return "" if self.is_test? and not OS.mac?
|
55
|
+
`xcode-select -p`.gsub("\n", '') + "/"
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.gem_path
|
59
|
+
if not Helper.is_test? and Gem::Specification::find_all_by_name('fastlane').any?
|
60
|
+
return Gem::Specification.find_by_name('fastlane').gem_dir
|
61
|
+
else
|
62
|
+
return './'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module Fastlane
|
4
|
+
class JUnitGenerator
|
5
|
+
def self.generate(results)
|
6
|
+
# JUnit file documentation: http://llg.cubic.org/docs/junit/
|
7
|
+
# And http://nelsonwells.net/2012/09/how-jenkins-ci-parses-and-displays-junit-output/
|
8
|
+
|
9
|
+
containing_folder = Fastlane::FastlaneFolder.path || Dir.pwd
|
10
|
+
path = File.join(containing_folder, "report.xml")
|
11
|
+
|
12
|
+
|
13
|
+
builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
14
|
+
xml.testsuites(name: "fastlane") {
|
15
|
+
xml.testsuite(name: "deploy") {
|
16
|
+
results.each_with_index do |current, index|
|
17
|
+
xml.testcase(name: [index, current[:name]].join(": "), time: current[:time]) {
|
18
|
+
xml.failure(message: current[:error]) if current[:error]
|
19
|
+
xml.system_out current[:output] if current[:output]
|
20
|
+
}
|
21
|
+
end
|
22
|
+
}
|
23
|
+
}
|
24
|
+
end
|
25
|
+
result = builder.to_xml.gsub("system_", "system-").gsub("", " ") # Jenkins can not parse 'ESC' symbol
|
26
|
+
|
27
|
+
File.write(path, result)
|
28
|
+
|
29
|
+
return path
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Fastlane
|
2
|
+
class LaneManager
|
3
|
+
def self.cruise_lanes(lanes)
|
4
|
+
raise "lanes must be an array" unless lanes.kind_of?Array
|
5
|
+
ff = Fastlane::FastFile.new(File.join(Fastlane::FastlaneFolder.path, 'Fastfile'))
|
6
|
+
|
7
|
+
if lanes.count == 0
|
8
|
+
raise "Please pass the name of the lane you want to drive. Available lanes: #{ff.runner.available_lanes.join(', ')}".red
|
9
|
+
end
|
10
|
+
|
11
|
+
start = Time.now
|
12
|
+
e = nil
|
13
|
+
begin
|
14
|
+
lanes.each do |key|
|
15
|
+
ff.runner.execute(key)
|
16
|
+
end
|
17
|
+
rescue => ex
|
18
|
+
if Actions.lane_context.count > 0
|
19
|
+
Helper.log.info "Variable Dump:".yellow
|
20
|
+
Helper.log.info Actions.lane_context
|
21
|
+
end
|
22
|
+
Helper.log.fatal ex
|
23
|
+
e = ex
|
24
|
+
end
|
25
|
+
|
26
|
+
# Finished with all the lanes
|
27
|
+
Fastlane::JUnitGenerator.generate(Fastlane::Actions.executed_actions)
|
28
|
+
|
29
|
+
duration = ((Time.now - start) / 60.0).round
|
30
|
+
|
31
|
+
unless e
|
32
|
+
Helper.log.info "fastlane.tools just saved you #{duration} minutes! 🎉".green
|
33
|
+
else
|
34
|
+
Helper.log.fatal "fastlane finished with errors".red
|
35
|
+
raise e
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|