Class: Asgard::Base

Inherits:
Thor
  • Object
show all
Includes:
Shell
Defined in:
lib/asgard/base.rb

Direct Known Subclasses

Tasks

Class Method Summary collapse

Methods included from Shell

#sh, #shebang

Class Method Details

._build_dep_graph(stages) ⇒ Object

Translate stages into a DependencyGraph-compatible hash.

stages: [[:one], [:two, :three], [:four]]
→ { one: [], two: [:one], three: [:one], four: [:two, :three] }


61
62
63
64
65
66
67
68
# File 'lib/asgard/base.rb', line 61

def _build_dep_graph(stages)
  graph = {}
  stages.each_with_index do |stage, i|
    prev_stage = i.positive? ? stages[i - 1] : []
    stage.each { |task| graph[task] = prev_stage.dup }
  end
  graph
end

._condObject



40
41
42
# File 'lib/asgard/base.rb', line 40

def _cond
  @_cond ||= Hash.new { |h, k| h[k] = ConditionVariable.new }
end

._depsObject



28
29
30
# File 'lib/asgard/base.rb', line 28

def _deps
  @_deps ||= {}
end

._doneObject



36
37
38
# File 'lib/asgard/base.rb', line 36

def _done
  @_done ||= Set.new
end

._ran_mutexObject



44
45
46
# File 'lib/asgard/base.rb', line 44

def _ran_mutex
  @_ran_mutex ||= Mutex.new
end

._reset_ran!Object

Reset execution tracking for a fresh asgard invocation.



49
50
51
52
53
54
55
# File 'lib/asgard/base.rb', line 49

def _reset_ran!
  _ran_mutex.synchronize do
    @_running = Set.new
    @_done    = Set.new
    @_cond    = Hash.new { |h, k| h[k] = ConditionVariable.new }
  end
end

._runningObject



32
33
34
# File 'lib/asgard/base.rb', line 32

def _running
  @_running ||= Set.new
end

.default_task(meth = nil) ⇒ Object



100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/asgard/base.rb', line 100

def default_task(meth = nil)
  if meth && meth != :none && @_default_task_location
    here = caller_locations(1, 1).first
    warn "asgard: default_task :#{meth} at #{here.path}:#{here.lineno} " \
         "overrides default_task :#{@_default_task_name} set at " \
         "#{@_default_task_location.path}:#{@_default_task_location.lineno}"
  end
  if meth && meth != :none
    @_default_task_location = caller_locations(1, 1).first
    @_default_task_name     = meth
  end
  super
end

.depends_on(*tasks) ⇒ Object

Declare dependencies for the next task. Bare symbols run sequentially; arrays within the splat run in parallel.

depends_on :build                          # sequential
depends_on :build, :lint                   # both sequential
depends_on [:build, :lint]                 # build and lint in parallel
depends_on :setup, [:build, :lint], :test  # setup, then build+lint, then test


77
78
79
# File 'lib/asgard/base.rb', line 77

def depends_on(*tasks)
  @_pending_deps = tasks
end

.desc(usage_or_desc, description = nil, options = {}) ⇒ Object

Allow single-argument desc: desc “Run the tests” The usage string defaults to the method name when the description is the only arg.



83
84
85
86
87
88
89
90
91
92
93
# File 'lib/asgard/base.rb', line 83

def desc(usage_or_desc, description = nil, options = {})
  if description.nil? || description.is_a?(Hash)
    options = description if description.is_a?(Hash)
    @_pending_single_desc      = usage_or_desc
    @_pending_single_desc_opts = options
  else
    @_pending_single_desc      = nil
    @_pending_single_desc_opts = nil
    super
  end
end

.dotenv(path = ".env") ⇒ Object



95
96
97
98
# File 'lib/asgard/base.rb', line 95

def dotenv(path = ".env")
  require "dotenv"
  Dotenv.load(path) if File.exist?(path)
end

.inherited(subclass) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/asgard/base.rb', line 15

def inherited(subclass)
  super
  Asgard::Base.subclasses << subclass
  subclass.instance_variable_set(:@_deps,              {})
  subclass.instance_variable_set(:@_pending_deps,      [])
  subclass.instance_variable_set(:@_pending_single_desc,      nil)
  subclass.instance_variable_set(:@_pending_single_desc_opts, nil)
  subclass.instance_variable_set(:@_running,    Set.new)
  subclass.instance_variable_set(:@_done,       Set.new)
  subclass.instance_variable_set(:@_cond,       Hash.new { |h, k| h[k] = ConditionVariable.new })
  subclass.instance_variable_set(:@_ran_mutex,  Mutex.new)
end

.method_added(method_name) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/asgard/base.rb', line 164

def method_added(method_name)
  if @_pending_single_desc && !no_commands?
    pending_desc = @_pending_single_desc
    pending_opts = @_pending_single_desc_opts || {}
    @_pending_single_desc      = nil
    @_pending_single_desc_opts = nil
    desc(method_name.to_s, pending_desc, pending_opts)
  end

  return super unless @usage

  pending = Array(@_pending_deps).dup
  @_pending_deps = []

  return super if pending.empty?
  return super if method_name.to_s.start_with?("_")

  # Each element is a Symbol (sequential) or Array (parallel group).
  _deps[method_name.to_sym] = pending.map { |d| Array(d).map(&:to_sym) }
  super
end

.subclassesObject



11
12
13
# File 'lib/asgard/base.rb', line 11

def subclasses
  @subclasses ||= []
end

.validate_deps!Object

Validate the full dep graph for cycles using Dagwood::DependencyGraph.



115
116
117
118
119
120
121
122
123
124
125
# File 'lib/asgard/base.rb', line 115

def validate_deps!
  _check_orphaned_deps!
  return if _deps.empty?

  all_task_names = all_commands.keys.map(&:to_sym)
  _check_undefined_deps!(all_task_names)
  _check_dep_arities!
  _build_and_sort_graph(all_task_names)
rescue TSort::Cyclic => e
  raise Asgard::CircularDependencyError, e.message
end