class Highway::Runtime::Runner
This class is responsible for evaluating invocation parameters, then validating and running step invocations.
Public Class Methods
new(manifest:, context:, interface:)
click to toggle source
Initialize an instance.
@parameter context [Highway::Runtime::Context] The runtime context. @parameter manifest [Highway::Compiler::Build::Output::Manifest] The build manifest. @parameter manifest [Highway::Interface] The interface.
# File lib/highway/runtime/runner.rb, line 30 def initialize(manifest:, context:, interface:) @manifest = manifest @context = context @interface = interface end
Public Instance Methods
run()
click to toggle source
Run the build manifest.
The runner prevalidates the step invocations if they don't contain any environments variables, then runs step invocations.
This method catches exceptions thrown inside steps in order to maintain control over the execution policy.
# File lib/highway/runtime/runner.rb, line 43 def run() # Validate invocations before running them. At this point we're able # to evaluate non-environment variables, typecheck and validate the # parameters. prevalidate_invocations() # Prepare the artifacts directory. If it doesn't exist, it's created at # this point. If it exist, it's removed and re-created. prepare_artifacts_dir() # Run invocations one-by-one. run_invocations() # Print the metrics table containing the status of invocation, its step # name and a duration it took. report_metrics() # Now it's time to raise an error if any of the steps failed. if @context.reports_any_failed? @interface.fatal!(nil) end end
Private Instance Methods
evaluate_parameter(value)
click to toggle source
# File lib/highway/runtime/runner.rb, line 203 def evaluate_parameter(value) if value.is_a?(Compiler::Analyze::Tree::Values::Primitive) value.segments.reduce("") { |memo, segment| if segment.is_a?(Compiler::Analyze::Tree::Segments::Text) memo + segment.value elsif segment.is_a?(Compiler::Analyze::Tree::Segments::Variable) && segment.scope == :env if @context.env.include?(segment.name) memo + @context.env.find(segment.name) || "" else @interface.fatal!("The value for key '#{segment.name}' does not exist.") end end } elsif value.is_a?(Compiler::Analyze::Tree::Values::Array) value.children.map { |value| evaluate_parameter(value) } elsif value.is_a?(Compiler::Analyze::Tree::Values::Hash) Utilities::hash_map(value.children) { |key, value| [key, evaluate_parameter(value)] } end end
evaluate_parameter_for_prevalidation(value)
click to toggle source
# File lib/highway/runtime/runner.rb, line 227 def evaluate_parameter_for_prevalidation(value) if value.is_a?(Compiler::Analyze::Tree::Values::Primitive) if value.select_variable_segments_with_scope(:env).empty? evaluate_parameter(value) else :ignore end elsif value.is_a?(Compiler::Analyze::Tree::Values::Array) value.children.map { |value| evaluate_parameter_for_prevalidation(value) } elsif value.is_a?(Compiler::Analyze::Tree::Values::Hash) Utilities::hash_map(value.children) { |key, value| [key, evaluate_parameter_for_prevalidation(value)] } end end
prepare_artifacts_dir()
click to toggle source
# File lib/highway/runtime/runner.rb, line 91 def prepare_artifacts_dir() FileUtils.remove_entry(@context.artifacts_dir) if File.exist?(@context.artifacts_dir) FileUtils.mkdir(@context.artifacts_dir) end
prevalidate_invocations()
click to toggle source
# File lib/highway/runtime/runner.rb, line 75 def prevalidate_invocations() @interface.header_success("Validating step parameters...") @manifest.invocations.each { |invocation| invocation.step_class.root_parameter.typecheck_and_prevalidate( evaluate_parameter_for_prevalidation(invocation.parameters), interface: @interface, keypath: invocation.keypath, ) } @interface.success("All step parameters passed initial validation.") end
report_metrics()
click to toggle source
# File lib/highway/runtime/runner.rb, line 245 def report_metrics() rows = @context.reports.each.map { |report| status = case report.result when :success then report.invocation.index.to_s when :failure then "x" when :skip then "-" end name = report.invocation.step_class.name minutes = (report.duration / 60).floor seconds = report.duration % 60 duration = "#{minutes}m #{seconds}s" if minutes > 0 duration ||= "#{seconds}s" row = [status, name, duration].map { |text| case report.result when :success then text.green when :failure then text.red.bold when :skip then text.yellow end } row } @interface.table( title: "Highway Summary".yellow, headings: ["", "Step", "Duration"], rows: rows ) end
run_invocation(invocation:)
click to toggle source
# File lib/highway/runtime/runner.rb, line 103 def run_invocation(invocation:) # Prepare a report instance and some temporary variables to be used in # metrics. report = Report.new( invocation: invocation, artifacts_dir: @context.artifacts_dir, ) step_name = invocation.step_class.name time_started = Time.now if !@context.reports_any_failed? || invocation.policy == :always # Only run the step if no previous step has failed or if the step # should always be executed. @interface.header_success("Running step: #{step_name}...") begin # Evaluate, typecheck and map the invocation parameters. At this # point we're able to evaluate environment variables. evaluated_parameters = Utilities::hash_map(invocation.parameters.children) { |name, value| [name, evaluate_parameter(value)] } parameters = invocation.step_class.root_parameter.typecheck_and_validate( evaluated_parameters, interface: @interface, keypath: invocation.keypath, ) # Run the step invocation. This is where steps are executed. invocation.step_class.run( parameters: parameters, context: @context, report: report, ) report.result = :success rescue FastlaneCore::Interface::FastlaneError, FastlaneCore::Interface::FastlaneCommonException => error # These two errors should not count as crashes and should not print # backtrace unless running in verbose mode. This follows the # behavior of `Fastlane::Runner`. @interface.error(error.message) @interface.error(error.backtrace.join("\n")) if @context.env.verbose? report.result = :failure report.error = error rescue FastlaneCore::Interface::FastlaneShellError => error # This error should be treated in a special way as its message # contains the whole command output. It should only be printed in # verbose mode. @interface.error(error.message.split("\n").first) @interface.error(error.message.split("\n").drop(1).join("\n")) if @context.env.verbose? @interface.error(error.backtrace.join("\n")) if @context.env.verbose? report.result = :failure report.error = error rescue => error # Chances are that this is `FastlaneCore::Interface::FastlaneCrash` # but it could be another error as well. For these error a backtrace # should always be printed. This follows the behavior of # `Fastlane::Runner`. @interface.error("#{error.class}: #{error.message}") @interface.error(error.backtrace.join("\n")) report.result = :failure report.error = error end else @interface.header_warning("Skipping step: #{step_name}...") @interface.warning("Skipping because a previous step has failed.") report.result = :skip end report.duration = (Time.now - time_started).round report end
run_invocations()
click to toggle source
# File lib/highway/runtime/runner.rb, line 96 def run_invocations() @manifest.invocations.each do |invocation| report = run_invocation(invocation: invocation) @context.add_report(report) end end