fastlane 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ module Fastlane
2
+ module Actions
3
+ class SayAction
4
+ def self.run(params)
5
+ text = params.join(' ')
6
+ Actions.sh("say '#{text}'")
7
+ end
8
+ end
9
+ end
10
+ end
@@ -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,10 @@
1
+ module Fastlane
2
+ module Actions
3
+ class XctoolAction
4
+ def self.run(params)
5
+ raise "xctool not installed, please install using `brew install xctool`".red if `which xctool`.length == 0
6
+ Actions.sh("xctool " + params.join(" "))
7
+ end
8
+ end
9
+ end
10
+ 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