Class: Detroit::Toolchain::Runner

Inherits:
Object
  • Object
show all
Defined in:
lib/detroit/toolchain/runner.rb

Overview

Assembly::Runner class is the main controller class for running a session of Detroit.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ Runner

Create a new Detroit Application instance.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/detroit/toolchain/runner.rb', line 28

def initialize(options)
  @options = options

  self.skip       = options[:skip]
  self.quiet      = options[:quiet]
  self.assembly   = options[:assembly]
  self.multitask  = options[:multitask]

  self.toolchain_files = options[:toolchains]

  @toolchains = {}
  @tools      = {}
  @defaults   = {}

  @loaded_plugins = {}

  #load_plugins
  #load_defaults
  load_toolchains
end

Instance Attribute Details

#defaultsHash

Custom service defaults. This is a mapping of service names to default settings. Very useful for when using the same service more than once.

Returns:

  • (Hash)

    default settings



133
134
135
# File 'lib/detroit/toolchain/runner.rb', line 133

def defaults
  @defaults
end

#optionsObject (readonly)

Options (generally from #cli).



25
26
27
# File 'lib/detroit/toolchain/runner.rb', line 25

def options
  @options
end

#toolchainsArray<String> (readonly)

The list of a project’s assembly files.

Returns:



121
122
123
# File 'lib/detroit/toolchain/runner.rb', line 121

def toolchains
  @toolchains
end

#toolsHash (readonly)

Tool configurations from Assembly or *.assembly files.

Returns:

  • (Hash)

    service settings



126
127
128
# File 'lib/detroit/toolchain/runner.rb', line 126

def tools
  @tools
end

Instance Method Details

#active_workers(track = nil) ⇒ Array<Worker>

Active workers are tool instance configured in a project’s assembly files that do not have their active setting turned off.

Returns:



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/detroit/toolchain/runner.rb', line 183

def active_workers(track=nil)
  @active_workers ||= (
    list = []

    tools.each do |key, opts|
      next unless opts
      next unless opts['active'] != false

      if opts['track']
        next unless opts['track'].include?((track || 'main').to_s)
      end

      next if skip.include?(key.to_s)

      if opts.key?('tool') && opts.key?('tooltype')
        abort "Two tool types given for `#{key}'."
      end

      # TODO: Ultimately deprecate completely.
      if opts.key?('service')
        abort "The `service` setting has been renamed. " +
              "Use `tool` or `tooltype` for `#{key}' instead."
      end

      tool_type = (
        opts.delete('class') || 
        opts.delete('tool')  ||
        key
      ).to_s.downcase

      unless Detroit.tools.key?(tool_type)
        load_plugin(tool_type)
      end

      tool_class = Detroit.tools[tool_type]

      abort "Unknown tool `#{tool_type}'." unless tool_class

      if tool_class.available? #(project)
        #opts = inject_environment(opts) # TODO: DEPRECATE
        options = defaults[tool_type.downcase].to_h
        options = options.merge(common_tool_options)
        options = options.merge(opts)

        options['project'] = project

        list << Worker.new(key, tool_class, options) #script,
      #else
      #  warn "Worker #{tool_class} is not available."
      end
    end

    # sorting here trickles down to processing later
    #list = list.sort_by{ |s| s.priority || 0 }

    list
  )
end

#ansize(text, code) ⇒ Object



496
497
498
499
500
501
502
503
# File 'lib/detroit/toolchain/runner.rb', line 496

def ansize(text, code)
  #return text unless text.color
  if RUBY_PLATFORM =~ /win/
    text.to_s
  else
    ANSI::Code.send(code.to_sym) + text
  end
end

#assemblyObject

Name of the assembly (default is ‘:standard`).



74
75
76
# File 'lib/detroit/toolchain/runner.rb', line 74

def assembly
  @assembly
end

#assembly=(name) ⇒ Object

Set assembly system to use.



79
80
81
# File 'lib/detroit/toolchain/runner.rb', line 79

def assembly=(name)
  @assembly = (name || DEFAULT_TOOLCHAIN).to_sym
end

#common_tool_optionsObject

TODO: Do we need verbose?



336
337
338
339
340
341
342
343
344
# File 'lib/detroit/toolchain/runner.rb', line 336

def common_tool_options
  {
    'trial'   => options[:trial],
    'trace'   => options[:trace],
    'quiet'   => options[:quiet],
    'force'   => options[:force],
    'verbose' => options[:verbose]
  }
end

#config_template(name) ⇒ Object

Generates a configuration template for any particular tool. This is only used for reference purposes.



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/detroit/toolchain/runner.rb', line 159

def config_template(name)
  if not Detroit.tools.key?(name)
    load_plugin(name)
  end
  list = {name => Detroit.tools[name]}
  cfg = {}
  list.each do |tool_name, tool_class|
    attrs = tool_class.options #instance_methods.select{ |m| m.to_s =~ /\w+=$/ && !%w{taguri=}.include?(m.to_s) }
    atcfg = attrs.inject({}){ |h, m| h[m.to_s.chomp('=')] = nil; h }
    atcfg['tool']    = tool_class.basename.downcase
    atcfg['active']  = false
    cfg[tool_name] = atcfg
  end
  cfg
end

#display_action(action_item) ⇒ Object



435
436
437
438
439
# File 'lib/detroit/toolchain/runner.rb', line 435

def display_action(action_item)
  phase, service, action, parameters = *action_item
  puts "  %-10s %-10s %-10s" % [phase.to_s.capitalize, service.service_title, action]
  #status_line(service.service_title, phase.to_s.capitalize)
end

#display_help(name) ⇒ void

This method returns an undefined value.

Display detailed help for a given tool.



143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/detroit/toolchain/runner.rb', line 143

def display_help(name)
  if not Detroit.tools.key?(name)
    load_plugin(name)
  end
  tool = Detroit.tools[name]
  if tool.const_defined?(:MANPAGE)
    man_page = tool.const_get(:MANPAGE)
    Kernel.system "man #{man_page}"
  else
    puts "No detailed help available for `#{name}'."
  end
end

#header_messageObject

— Print Methods ——————————————————-



396
397
398
399
400
401
402
# File 'lib/detroit/toolchain/runner.rb', line 396

def header_message
  if multitask?
    ["#{project..title} v#{project..version}   [M]", "#{project.root}"]
  else
    ["#{project..title} v#{project..version}", "#{project.root}"]
  end
end

#load_plugin(name) ⇒ Object

Load a plugin.



520
521
522
523
524
525
526
527
528
529
530
531
# File 'lib/detroit/toolchain/runner.rb', line 520

def load_plugin(name)
  @loaded_plugins[name] ||= (
    begin
      require "detroit-#{name}"
    rescue LoadError => e
      $stderr.puts "ERROR: #{e.message.capitalize}"
      $stderr.puts "       Perhaps `gem install detroit-#{name}`?"
      exit -1
    end
    name # true ?
  )
end

#load_toolchain_file(file) ⇒ Object

Load toolchain file.



575
576
577
578
# File 'lib/detroit/toolchain/runner.rb', line 575

def load_toolchain_file(file)
  @toolchains[file] = Toolchain::Script.load(File.new(file), project)
  @tools.merge!(toolchains[file].tools)
end

#load_toolchainsObject



552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
# File 'lib/detroit/toolchain/runner.rb', line 552

def load_toolchains
  toolchain_filenames.each do |file|
    load_toolchain_file(file)
  end

  #if config = eval('self', TOPLEVEL_BINDING).rc_detroit
  #  @toolchains['(rc)'] = Script.new(&config)
  #  @tools.merge!(toolchains['(rc)'].tools)
  #end

  #if config = Detroit.rc_config
  #  tc = Script.new do
  #    tools.each do |c|
  #      track(c.profile, &c)
  #    end
  #  end
  #  @toolchains['(rc)'] = tc
  #  @tools.merge!(toolchains['(rc)'].tools)
  #end
end

#multitask=(boolean) ⇒ Object

Set multi-task mode.



89
90
91
92
93
94
95
96
# File 'lib/detroit/toolchain/runner.rb', line 89

def multitask=(boolean)
  if boolean && !defined?(Parallel)
    puts "Parallel gem must be installed to multitask."
    @multitask = false
  else
    @multitask = boolean
  end
end

#multitask?Boolean

Multitask mode?

Returns:

  • (Boolean)


84
85
86
# File 'lib/detroit/toolchain/runner.rb', line 84

def multitask?
  @multitask      
end


442
443
444
445
446
447
448
449
450
# File 'lib/detroit/toolchain/runner.rb', line 442

def print_header(left, right)
  if $ansi #ANSI::SUPPORTED
    printline('', '', :pad=>1, :sep=>' ', :style=>[:negative, :bold], :left=>[:bold], :right=>[:bold])
    printline(left, right, :pad=>2, :sep=>' ', :style=>[:negative, :bold], :left=>[:bold], :right=>[:bold])
    printline('', '', :pad=>1, :sep=>' ', :style=>[:negative, :bold], :left=>[:bold], :right=>[:bold])
  else
    printline(left, right, :pad=>2, :sep=>'=')
  end
end


453
454
455
456
457
458
459
# File 'lib/detroit/toolchain/runner.rb', line 453

def print_phase(left, right)
  if $ansi #ANSI::SUPPORTED
    printline(left, right, :pad=>2, :sep=>' ', :style=>[:on_white, :black, :bold], :left=>[:bold], :right=>[:bold])
  else
    printline(left, right, :pad=>2, :sep=>'-')
  end
end

#printline(left, right = '', options = {}) ⇒ Object



462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
# File 'lib/detroit/toolchain/runner.rb', line 462

def printline(left, right='', options={})
  return if quiet?

  separator = options[:seperator] || options[:sep] || ' '
  padding   = options[:padding]   || options[:pad] || 0

  left, right = left.to_s, right.to_s

  left_size  = left.size
  right_size = right.size

  #left  = colorize(left)
  #right = colorize(right)

  l = padding
  r = -(right_size + padding)

  style  = options[:style] || []
  lstyle = options[:left]  || []
  rstyle = options[:right] || []

  left  = lstyle.inject(left) { |s, c| ansize(s, c) }
  right = rstyle.inject(right){ |s, c| ansize(s, c) }

  line = separator * screen_width
  line[l, left_size]  = left  if left_size != 0
  line[r, right_size] = right if right_size != 0

  line = style.inject(line){ |s, c| ansize(s, c) }

  puts line + ansize('', :clear)
end

#projectObject

Provides access to the Project instance via ‘Detroit.project` class method.



109
110
111
# File 'lib/detroit/toolchain/runner.rb', line 109

def project
  @project ||= Detroit.project(root)
end

#quiet=(boolean) ⇒ Boolean

Set quiet mode.

Returns:

  • (Boolean)


59
60
61
# File 'lib/detroit/toolchain/runner.rb', line 59

def quiet=(boolean)
  @quiet = !!boolean
end

#quiet?Boolean

Quiet mode?

Returns:

  • (Boolean)


52
53
54
# File 'lib/detroit/toolchain/runner.rb', line 52

def quiet?
  @quiet
end

#rootObject

TODO: Lookup project root.



513
514
515
# File 'lib/detroit/toolchain/runner.rb', line 513

def root
  Pathname.new(Dir.pwd)
end

#run(stop) ⇒ Object

Run up to the specified track stop.



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/detroit/toolchain/runner.rb', line 250

def run(stop)
  raise "Malformed destination -- #{stop}" unless /^\w+\:{0,1}\w+$/ =~ stop

  track, stop = stop.split(':')
  track, stop = 'main', track unless stop

  track = track.to_sym
  stop  = stop.to_sym if stop

  # TODO: Using #preconfigure as part of the protocol should probably change.

  ## prime the workers (so as to fail early)
  active_workers(track).each do |w|
    w.preconfigure if w.respond_to?("preconfigure")
  end

  sys = Detroit.assemblies[assembly.to_sym]

  raise "Unknown assembly `#{assembly}'" unless sys

  # Lookup chain by stop name.
  chain = sys.find(stop)

  #if stop
  #  system = track.route_with_stop(stop)
  #  raise "Unknown stop -- #{stop}" unless system

  unless chain
    #overview
    $stderr.puts "Unknown stop `#{stop}'."
    exit 0
  end

  @destination = stop

  status_header(*header_message)

  start_time = Time.now

  chain.each do |run_stop|
    next if skip.include?("#{run_stop}")  # TODO: Should we really allow skipping stops?
    #tool_hooks(name, ('pre_' + run_stop.to_s).to_sym)
    tool_calls(track, ('pre_' + run_stop.to_s).to_sym)
    tool_calls(track, run_stop)
    tool_calls(track, ('aft_' + run_stop.to_s).to_sym)
    #tool_hooks(name, ('aft_' + run_stop.to_s).to_sym)
    break if stop == run_stop
  end

  stop_time = Time.now

  puts "\nFinished in #{stop_time - start_time} seconds." unless quiet?
end

#run_a_worker(worker, track, stop) ⇒ void

TODO:

Provide more robust options, rather than just ‘@destination`.

This method returns an undefined value.

Invoke a worker given the worker, track and stop name.

TODO: Rename this method.



381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/detroit/toolchain/runner.rb', line 381

def run_a_worker(worker, track, stop)
  if target = worker.stop?(stop, @destination)
    target = stop if TrueClass === target
    label  = stop.to_s.gsub('_', '-').capitalize
    if options[:trace] #options[:verbose]
      status_line("#{worker.key.to_s} (#{worker.class}##{target})", label)
    else
      status_line("#{worker.key.to_s}", label)
    end
    worker.invoke(target, @destination)
  end
end

#screen_widthInteger

Get the terminals width.

Returns:

  • (Integer)


508
509
510
# File 'lib/detroit/toolchain/runner.rb', line 508

def screen_width
  ANSI::Terminal.terminal_width
end

#skipObject

List of tool names to skip.



64
65
66
# File 'lib/detroit/toolchain/runner.rb', line 64

def skip
  @skip
end

#skip=(list) ⇒ Object

Set skip list.



69
70
71
# File 'lib/detroit/toolchain/runner.rb', line 69

def skip=(list)
  @skip = list.to_list.map{ |s| s.downcase }
end

#start(stop) ⇒ Object

Change direectory to project root and run.



243
244
245
246
247
# File 'lib/detroit/toolchain/runner.rb', line 243

def start(stop)
  Dir.chdir(project.root) do       # change into project directory
    run(stop)
  end
end

#status_header(left, right = '') ⇒ Object

Print a status header, which consists of project name and version on the left and stop location on the right.



407
408
409
410
411
412
413
414
415
416
# File 'lib/detroit/toolchain/runner.rb', line 407

def status_header(left, right='')
  left, right = left.to_s, right.to_s
  #left.color  = 'blue'
  #right.color = 'magenta'
  unless quiet?
    puts
    print_header(left, right)
    #puts "=" * io.screen_width
  end
end

#status_line(left, right = '') ⇒ Object

Print a status line, which consists of worker name on the left and stop name on the right.



421
422
423
424
425
426
427
428
429
430
431
432
# File 'lib/detroit/toolchain/runner.rb', line 421

def status_line(left, right='')
  left, right = left.to_s, right.to_s
  #left.color  = 'blue'
  #right.color = 'magenta'
  unless quiet?
    puts
    #puts "-" * io.screen_width
    print_phase(left, right)
    #puts "-" * io.screen_width
    #puts
  end
end

#tool_calls(track, stop) ⇒ Object

Make tool calls.

This groups workers by priority b/c groups of the same priority can be run in parallel if the multitask option is on.



351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/detroit/toolchain/runner.rb', line 351

def tool_calls(track, stop)
  prioritized_workers = active_workers(track).group_by{ |w| w.priority }.sort_by{ |k,v| k }
  prioritized_workers.each do |priority, workers|
    ## remove any workers specified by the --skip option on the comamndline
    #workers = workers.reject{ |w| skip.include?(w.key.to_s) }

    ## only servies that are on the track
    #workers = workers.select{ |w| w.tracks.nil? or w.tracks.include?(w.to_s) }

    worklist = workers.map{ |w| [w, track, stop] }

    if multitask?
      results = Parallel.in_processes(worklist.size) do |i|
        run_a_worker(*worklist[i])
      end
    else
      worklist.each do |args|
        run_a_worker(*args)
      end
    end
  end
end

#tool_class_options(tool_class) ⇒ Object



176
177
# File 'lib/detroit/toolchain/runner.rb', line 176

def tool_class_options(tool_class)
end

#toolchain_filenamesObject

If a ‘Toolchain` or `.toolchain` file exists, then it is returned. Otherwise all `*.toolchain` files are loaded. To load `*.toolchain` files from another directory add the directory to config options file.

TODO: Simplify this to just ‘toolchain`.



586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
# File 'lib/detroit/toolchain/runner.rb', line 586

def toolchain_filenames
  @toolchain_filenames ||= (
    files = []
    ## match 'Toolchain' or '.toolchain' file
    files = project.root.glob("{,.,*.}#{FILE_EXTENSION}{,.rb,.yml,.yaml}", :casefold)
    ## only files
    files = files.select{ |f| File.file?(f) }
    ## 
    if files.empty?
      ## match '.detroit/*.toolchain' or 'detroit/*.toolchain'
      #files += project.root.glob("{,.}#{DIRECTORY}/*.#{FILE_EXTENSION}", :casefold)
      ## match 'task/*.toolchain' (OLD SCHOOL)
      files += project.root.glob("{task,tasks}/*.#{FILE_EXTENSION}", :casefold)
      ## only files
      files = files.select{ |f| File.file?(f) }
    end
    files
  )
end

#toolchain_filesObject

List of toolchain files to use.



99
100
101
# File 'lib/detroit/toolchain/runner.rb', line 99

def toolchain_files
  @toolchain_files
end

#toolchain_files=(files) ⇒ Object



104
105
106
# File 'lib/detroit/toolchain/runner.rb', line 104

def toolchain_files=(files)
  @toolchain_files = files
end