Class: Kettle::Jem::Appraisals::WorkflowStrategyGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/kettle/jem/appraisals/workflow_strategy_generator.rb,
sig/kettle/jem/appraisals.rbs

Overview

Generates GitHub Actions workflow strategy matrix entries from the resolved appraisal matrix.

Groups entries by workflow lifecycle category (+current+, supported, legacy, unsupported, ancient) based on Ruby bucket ranges.

Each matrix entry maps to a single CI job: a specific appraisal (tier1×tier2 version combo) on a specific Ruby version.

Lifecycle categories (kettle-jem convention)

current     — latest stable MRI ("ruby"), plus jruby/truffleruby
supported   — maintained MRI versions (currently 3.2, 3.3, 3.4)
legacy      — security-only MRI (3.0, 3.1)
unsupported — EOL but recent MRI (2.6, 2.7)
ancient     — very old MRI (2.3, 2.4, 2.5)

Examples:

gen = WorkflowStrategyGenerator.new(bucket_ranges: ranges, exec_cmd: "rake spec")
groups = gen.generate(appraisal_entries)
#=> { "current" => [{ruby: "ruby", appraisal: "kja-ar-8-r3", ...}], ... }

Constant Summary collapse

LIFECYCLE_RANGES =

Returns Ruby lifecycle boundaries mapping lifecycle name to min/max Gem::Version ranges (update as Ruby EOL progresses).

Returns:

  • (Hash{String => Hash})

    Ruby lifecycle boundaries mapping lifecycle name to min/max Gem::Version ranges (update as Ruby EOL progresses)

{
  "current" => {ruby_alias: "ruby", min: Gem::Version.new("3.4"), max: Gem::Version.new("3.99")},
  "supported" => {min: Gem::Version.new("3.2"), max: Gem::Version.new("3.3")},
  "legacy" => {min: Gem::Version.new("3.0"), max: Gem::Version.new("3.1")},
  "unsupported" => {min: Gem::Version.new("2.6"), max: Gem::Version.new("2.7")},
  "ancient" => {min: Gem::Version.new("2.3"), max: Gem::Version.new("2.5")}
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(bucket_ranges:, exec_cmd: "rake spec") ⇒ WorkflowStrategyGenerator

Returns a new instance of WorkflowStrategyGenerator.

Parameters:

  • bucket_ranges (Hash{String => Hash})

    bucket → {floor: Gem::Version, ceiling: Gem::Version}

  • exec_cmd (String) (defaults to: "rake spec")

    the test/build command (default: "rake spec")

  • bucket_ranges: (Hash[String, Hash[Symbol, Gem::Version]])
  • exec_cmd: (String) (defaults to: "rake spec")


46
47
48
49
# File 'lib/kettle/jem/appraisals/workflow_strategy_generator.rb', line 46

def initialize(bucket_ranges:, exec_cmd: "rake spec")
  @bucket_ranges = bucket_ranges
  @exec_cmd = exec_cmd
end

Instance Attribute Details

#bucket_rangesHash{String => Hash} (readonly)

Returns bucket → {floor: Gem::Version, ceiling: Gem::Version}.

Returns:

  • (Hash{String => Hash})

    bucket → {floor: Gem::Version, ceiling: Gem::Version}



39
40
41
# File 'lib/kettle/jem/appraisals/workflow_strategy_generator.rb', line 39

def bucket_ranges
  @bucket_ranges
end

#exec_cmdString (readonly)

Returns the shell command to execute in each CI matrix job.

Returns:

  • (String)

    the shell command to execute in each CI matrix job



42
43
44
# File 'lib/kettle/jem/appraisals/workflow_strategy_generator.rb', line 42

def exec_cmd
  @exec_cmd
end

Instance Method Details

#build_matrix_entry(entry, ruby_version) ⇒ Hash[Symbol, untyped]

Parameters:

  • entry (Hash[Symbol, untyped])
  • ruby_version (String)

Returns:

  • (Hash[Symbol, untyped])


122
123
124
125
126
127
128
129
130
131
# File 'lib/kettle/jem/appraisals/workflow_strategy_generator.rb', line 122

def build_matrix_entry(entry, ruby_version)
  {
    ruby: ruby_version,
    appraisal: entry[:appraisal_name] || entry[:name],
    exec_cmd: exec_cmd,
    gemfile: "Appraisal.root",
    rubygems: "latest",
    bundler: "latest"
  }
end

#generate(appraisal_entries) ⇒ Hash{String => Array<Hash>}

Groups appraisal entries into workflow lifecycle files with matrix entries.

Parameters:

  • appraisal_entries (Array<Hash>)

    entries from CLI build_matrix, each with :name, :ruby_series, and gemfile path keys. Entries may also provide :appraisal_name to reuse a standard kettle-jem appraisal.

Returns:

  • (Hash{String => Array<Hash>})

    lifecycle name → sorted array of matrix include entries, each with :ruby, :appraisal, :exec_cmd, :gemfile, :rubygems, and :bundler keys



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/kettle/jem/appraisals/workflow_strategy_generator.rb', line 59

def generate(appraisal_entries)
  grouped = Hash.new { |h, k| h[k] = [] }

  appraisal_entries.each do |entry|
    bucket = entry[:ruby_series]
    range = bucket_ranges[bucket]
    next unless range

    lifecycle = lifecycle_for(range[:floor])
    ruby_version = ruby_version_for(range[:floor], lifecycle)

    grouped[lifecycle] << build_matrix_entry(entry, ruby_version)
  end

  # Sort entries within each lifecycle by appraisal name
  grouped.transform_values { |entries| entries.sort_by { |e| e[:appraisal] } }
end

#generate_yaml_snippets(appraisal_entries) ⇒ Hash{String => String}

Generates YAML-compatible strategy.matrix.include snippets for each lifecycle.

Parameters:

  • appraisal_entries (Array<Hash>)

    entries from CLI build_matrix

Returns:

  • (Hash{String => String})

    lifecycle name → YAML strategy matrix snippet



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/kettle/jem/appraisals/workflow_strategy_generator.rb', line 81

def generate_yaml_snippets(appraisal_entries)
  groups = generate(appraisal_entries)

  groups.transform_values { |entries|
    lines = ["strategy:", "  matrix:", "    include:"]
    entries.each do |entry|
      lines << "      - ruby: #{entry[:ruby].inspect}"
      lines << "        appraisal: #{entry[:appraisal].inspect}"
      lines << "        exec_cmd: #{entry[:exec_cmd].inspect}"
      lines << "        gemfile: #{entry[:gemfile].inspect}"
      lines << "        rubygems: #{entry[:rubygems].inspect}"
      lines << "        bundler: #{entry[:bundler].inspect}"
    end
    lines.join("\n")
  }
end

#lifecycle_for(ruby_floor) ⇒ String

Determines which lifecycle a Ruby floor version belongs to.

Parameters:

  • ruby_floor (Gem::Version)

Returns:

  • (String)


101
102
103
104
105
106
107
108
# File 'lib/kettle/jem/appraisals/workflow_strategy_generator.rb', line 101

def lifecycle_for(ruby_floor)
  LIFECYCLE_RANGES.each do |name, range|
    return name if ruby_floor.between?(range[:min], range[:max])
  end
  # Fallback: if below all ranges, ancient; if above, current
  return "ancient" if ruby_floor < LIFECYCLE_RANGES["ancient"][:min]
  "current"
end

#ruby_version_for(ruby_floor, lifecycle) ⇒ String

Maps a Ruby floor version to the setup-ruby version string. For "current", uses "ruby" alias (latest stable). For all others, uses the explicit minor version (e.g., "3.2").

Parameters:

  • ruby_floor (Gem::Version)
  • lifecycle (String)

Returns:

  • (String)


113
114
115
116
117
118
119
120
# File 'lib/kettle/jem/appraisals/workflow_strategy_generator.rb', line 113

def ruby_version_for(ruby_floor, lifecycle)
  if lifecycle == "current"
    "ruby"
  else
    segs = ruby_floor.segments
    "#{segs[0]}.#{segs[1] || 0}"
  end
end