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] }


65
66
67
68
69
70
71
72
# File 'lib/asgard/base.rb', line 65

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

._condObject



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

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



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

def _done
  @_done ||= Set.new
end

._ran_mutexObject



48
49
50
# File 'lib/asgard/base.rb', line 48

def _ran_mutex
  @_ran_mutex ||= Mutex.new
end

._reset_ran!Object

Reset execution tracking for a fresh asgard invocation.



53
54
55
56
57
58
59
# File 'lib/asgard/base.rb', line 53

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



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

def _running
  @_running ||= Set.new
end

._varsObject



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

def _vars
  @_vars ||= {}
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


81
82
83
# File 'lib/asgard/base.rb', line 81

def depends_on(*tasks)
  @_pending_deps = tasks
end

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



104
105
106
107
# File 'lib/asgard/base.rb', line 104

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

.import(mod) ⇒ Object



100
101
102
# File 'lib/asgard/base.rb', line 100

def import(mod)
  include mod
end

.inherited(subclass) ⇒ Object



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

def inherited(subclass)
  super
  Asgard::Base.subclasses << subclass
  subclass.instance_variable_set(:@_deps,         {})
  subclass.instance_variable_set(:@_vars,         {})
  subclass.instance_variable_set(:@_pending_deps, [])
  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



146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/asgard/base.rb', line 146

def method_added(method_name)
  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



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

def subclasses
  @subclasses ||= []
end

.validate_deps!Object

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



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/asgard/base.rb', line 110

def validate_deps!
  pending = Array(@_pending_deps)
  if pending.any?
    raise Asgard::Error,
      "depends_on(#{pending.join(', ')}) declared without a following task definition"
  end

  return if _deps.empty?

  all_task_names = all_commands.keys.map(&:to_sym)
  full_graph     = all_task_names.each_with_object({}) do |task, hash|
    hash[task] = _deps.fetch(task, []).flatten
  end

  undefined = _deps.values.flatten.uniq - all_task_names
  if undefined.any?
    raise Asgard::Error, "undefined task(s) in depends_on: #{undefined.sort.join(', ')}"
  end

  _deps.each do |_task, stages|
    stages.flatten.each do |dep|
      meth = instance_method(dep.to_s) rescue nil
      next unless meth
      required = meth.parameters.count { |type, _| type == :req }
      if required > 0
        raise Asgard::Error,
          "task '#{dep}' has #{required} required argument(s) and cannot be used as a dependency"
      end
    end
  end

  Dagwood::DependencyGraph.new(full_graph).order
rescue TSort::Cyclic => e
  raise Asgard::CircularDependencyError, e.message
end

.var(name, value = nil, &block) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/asgard/base.rb', line 85

def var(name, value = nil, &block)
  value = block if block_given?
  _vars[name.to_sym] = value
  no_commands do
    define_method(name) do
      ivar = :"@__var_#{name}"
      unless instance_variable_defined?(ivar)
        v = self.class._vars[name.to_sym]
        instance_variable_set(ivar, v.respond_to?(:call) ? v.call : v)
      end
      instance_variable_get(ivar)
    end
  end
end