scout-gear 7.1.0 → 7.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. checksums.yaml +4 -4
  2. data/.vimproject +65 -2
  3. data/VERSION +1 -1
  4. data/bin/scout +5 -1
  5. data/lib/rbbt-scout.rb +5 -0
  6. data/lib/scout/concurrent_stream.rb +13 -8
  7. data/lib/scout/config.rb +168 -0
  8. data/lib/scout/exceptions.rb +5 -3
  9. data/lib/scout/indiferent_hash/options.rb +1 -0
  10. data/lib/scout/indiferent_hash.rb +4 -2
  11. data/lib/scout/log/color.rb +3 -2
  12. data/lib/scout/log/progress/report.rb +1 -0
  13. data/lib/scout/log/progress/util.rb +66 -1
  14. data/lib/scout/log/progress.rb +5 -3
  15. data/lib/scout/log.rb +3 -2
  16. data/lib/scout/misc/helper.rb +31 -0
  17. data/lib/scout/misc/monitor.rb +4 -1
  18. data/lib/scout/misc/system.rb +15 -0
  19. data/lib/scout/misc.rb +2 -0
  20. data/lib/scout/named_array.rb +68 -0
  21. data/lib/scout/open/stream.rb +58 -33
  22. data/lib/scout/path/find.rb +27 -3
  23. data/lib/scout/path/util.rb +7 -4
  24. data/lib/scout/persist/serialize.rb +7 -14
  25. data/lib/scout/persist.rb +46 -12
  26. data/lib/scout/resource/produce.rb +7 -94
  27. data/lib/scout/resource/software.rb +176 -0
  28. data/lib/scout/semaphore.rb +8 -1
  29. data/lib/scout/tsv/dumper.rb +112 -0
  30. data/lib/scout/tsv/index.rb +161 -0
  31. data/lib/scout/tsv/open.rb +128 -0
  32. data/lib/scout/tsv/parser.rb +230 -30
  33. data/lib/scout/tsv/path.rb +13 -0
  34. data/lib/scout/tsv/persist/adapter.rb +367 -0
  35. data/lib/scout/tsv/persist/fix_width_table.rb +324 -0
  36. data/lib/scout/tsv/persist/serialize.rb +117 -0
  37. data/lib/scout/tsv/persist/tokyocabinet.rb +113 -0
  38. data/lib/scout/tsv/persist.rb +13 -0
  39. data/lib/scout/tsv/traverse.rb +143 -0
  40. data/lib/scout/tsv/util/filter.rb +303 -0
  41. data/lib/scout/tsv/util/process.rb +73 -0
  42. data/lib/scout/tsv/util/select.rb +220 -0
  43. data/lib/scout/tsv/util.rb +82 -0
  44. data/lib/scout/tsv.rb +16 -3
  45. data/lib/scout/work_queue/worker.rb +4 -4
  46. data/lib/scout/work_queue.rb +22 -7
  47. data/lib/scout/workflow/definition.rb +101 -4
  48. data/lib/scout/workflow/step/config.rb +18 -0
  49. data/lib/scout/workflow/step/dependencies.rb +40 -0
  50. data/lib/scout/workflow/step/file.rb +15 -0
  51. data/lib/scout/workflow/step/info.rb +35 -4
  52. data/lib/scout/workflow/step/progress.rb +14 -0
  53. data/lib/scout/workflow/step/provenance.rb +148 -0
  54. data/lib/scout/workflow/step.rb +71 -17
  55. data/lib/scout/workflow/task.rb +10 -5
  56. data/lib/scout/workflow/usage.rb +3 -1
  57. data/lib/scout/workflow.rb +11 -3
  58. data/lib/scout-gear.rb +1 -0
  59. data/lib/scout.rb +1 -0
  60. data/scout-gear.gemspec +64 -10
  61. data/scout_commands/find +1 -1
  62. data/scout_commands/workflow/task +16 -9
  63. data/scout_commands/workflow/task_old +2 -2
  64. data/share/software/install_helpers +523 -0
  65. data/test/scout/log/test_progress.rb +0 -2
  66. data/test/scout/misc/test_system.rb +21 -0
  67. data/test/scout/open/test_stream.rb +160 -1
  68. data/test/scout/path/test_find.rb +14 -7
  69. data/test/scout/resource/test_software.rb +24 -0
  70. data/test/scout/test_config.rb +66 -0
  71. data/test/scout/test_meta_extension.rb +10 -0
  72. data/test/scout/test_named_array.rb +19 -0
  73. data/test/scout/test_persist.rb +96 -0
  74. data/test/scout/test_tmpfile.rb +1 -1
  75. data/test/scout/test_tsv.rb +50 -1
  76. data/test/scout/test_work_queue.rb +41 -13
  77. data/test/scout/tsv/persist/test_adapter.rb +44 -0
  78. data/test/scout/tsv/persist/test_fix_width_table.rb +134 -0
  79. data/test/scout/tsv/persist/test_tokyocabinet.rb +92 -0
  80. data/test/scout/tsv/test_dumper.rb +44 -0
  81. data/test/scout/tsv/test_index.rb +156 -0
  82. data/test/scout/tsv/test_open.rb +9 -0
  83. data/test/scout/tsv/test_parser.rb +114 -3
  84. data/test/scout/tsv/test_persist.rb +43 -0
  85. data/test/scout/tsv/test_traverse.rb +116 -0
  86. data/test/scout/tsv/test_util.rb +23 -0
  87. data/test/scout/tsv/util/test_filter.rb +188 -0
  88. data/test/scout/tsv/util/test_process.rb +47 -0
  89. data/test/scout/tsv/util/test_select.rb +44 -0
  90. data/test/scout/work_queue/test_worker.rb +66 -9
  91. data/test/scout/workflow/step/test_dependencies.rb +25 -0
  92. data/test/scout/workflow/step/test_info.rb +15 -17
  93. data/test/scout/workflow/step/test_load.rb +19 -21
  94. data/test/scout/workflow/step/test_provenance.rb +25 -0
  95. data/test/scout/workflow/test_step.rb +206 -10
  96. data/test/scout/workflow/test_task.rb +0 -3
  97. data/test/test_helper.rb +9 -1
  98. metadata +50 -6
@@ -21,27 +21,27 @@ class WorkQueue
21
21
  raise obj
22
22
  end
23
23
  res = block.call obj
24
- output.write res unless output.nil? || ignore_ouput || res == :ignore
24
+ output.write res unless ignore_ouput || res == :ignore
25
25
  end
26
26
  rescue DoneProcessing
27
27
  rescue Interrupt
28
28
  rescue Exception
29
29
  output.write WorkerException.new($!, Process.pid)
30
- exit -1
30
+ Kernel.exit -1
31
31
  end
32
32
  end
33
33
  end
34
34
 
35
35
  def abort
36
36
  begin
37
- Log.log "Aborting worker #{@pid}"
37
+ Log.debug "Aborting worker #{@pid}"
38
38
  Process.kill "INT", @pid
39
39
  rescue Errno::ECHILD
40
40
  end
41
41
  end
42
42
 
43
43
  def join
44
- Log.log "Joining worker #{@pid}"
44
+ Log.debug "Joining worker #{@pid}"
45
45
  Process.waitpid @pid
46
46
  end
47
47
 
@@ -36,8 +36,12 @@ class WorkQueue
36
36
 
37
37
  def remove_worker(pid)
38
38
  @worker_mutex.synchronize do
39
- @workers.delete_if{|w| w.pid == pid }
40
- @removed_workers << pid
39
+ worker = @workers.index{|w| w.pid == pid}
40
+ if worker
41
+ Log.debug "Removed worker #{pid}"
42
+ @workers.delete_at(worker)
43
+ @removed_workers << pid
44
+ end
41
45
  end
42
46
  end
43
47
 
@@ -50,11 +54,13 @@ class WorkQueue
50
54
  while true
51
55
  obj = @output.read
52
56
  if DoneProcessing === obj
57
+
53
58
  done = @worker_mutex.synchronize do
54
59
  Log.low "Worker #{obj.pid} done"
55
60
  @done_workers << obj.pid
56
- @done_workers.length == @removed_workers.length + @workers.length
61
+ @closed && @done_workers.length == @removed_workers.length + @workers.length
57
62
  end
63
+
58
64
  break if done
59
65
  elsif Exception === obj
60
66
  raise obj
@@ -65,9 +71,13 @@ class WorkQueue
65
71
  rescue DoneProcessing
66
72
  rescue Aborted
67
73
  rescue WorkerException
68
- Log.error "Exception in worker #{obj.pid} #{Log.fingerprint obj.exception}"
74
+ Log.error "Exception in worker #{obj.pid} in queue #{Process.pid}: #{obj.message}"
69
75
  self.abort
70
- raise obj.exception
76
+ raise obj.worker_exception
77
+ rescue
78
+ Log.error "Exception processing output in queue #{Process.pid}: #{$!.message}"
79
+ self.abort
80
+ raise $!
71
81
  end
72
82
  end
73
83
 
@@ -84,11 +94,12 @@ class WorkQueue
84
94
  while true
85
95
  pid = Process.wait
86
96
  remove_worker(pid)
87
- break if workers.empty?
97
+ break if @worker_mutex.synchronize{ @workers.empty? }
88
98
  end
89
99
  end
90
100
  end
91
101
 
102
+ Thread.pass until @worker_mutex.synchronize{ @workers.select{|w| w.pid.nil? }.empty? }
92
103
  Thread.pass until @waiter["name"]
93
104
  end
94
105
 
@@ -97,10 +108,14 @@ class WorkQueue
97
108
  end
98
109
 
99
110
  def abort
100
- workers.each{|w| w.abort }
111
+ Log.low "Aborting #{@workers.length} workers in queue #{Process.pid}"
112
+ @worker_mutex.synchronize do
113
+ @workers.each{|w| w.abort }
114
+ end
101
115
  end
102
116
 
103
117
  def close
118
+ @closed = true
104
119
  @worker_mutex.synchronize{ @workers.length }.times do
105
120
  @input.write DoneProcessing.new()
106
121
  end
@@ -2,7 +2,7 @@ require_relative '../meta_extension'
2
2
 
3
3
  module Workflow
4
4
  extend MetaExtension
5
- extension_attr :name, :tasks
5
+ extension_attr :name, :tasks, :helpers
6
6
 
7
7
  class << self
8
8
  attr_accessor :directory
@@ -17,6 +17,32 @@ module Workflow
17
17
  @name ||= self.to_s
18
18
  end
19
19
 
20
+ def helpers
21
+ @helpers ||= {}
22
+ end
23
+
24
+ def helper(name, *args, &block)
25
+ if block_given?
26
+ helpers[name] = block
27
+ else
28
+ raise RbbtException, "helper #{name} unkown in #{self} workflow" unless helpers[name]
29
+ helpers[name].call(*args)
30
+ end
31
+ end
32
+
33
+ def step_module
34
+ @_m ||= begin
35
+ m = Module.new
36
+
37
+ helpers.each do |name,block|
38
+ m.send(:define_method, name, &block)
39
+ end
40
+
41
+ m
42
+ end
43
+ @_m
44
+ end
45
+
20
46
  attr_accessor :directory
21
47
  def directory
22
48
  @directory ||= Workflow.directory[name]
@@ -60,19 +86,90 @@ module Workflow
60
86
  annotate_next_task(:inputs, args)
61
87
  end
62
88
 
89
+ def desc(description)
90
+ annotate_next_task_single(:description, description)
91
+ end
92
+
93
+ def returns(type)
94
+ annotate_next_task_single(:returns, type)
95
+ end
96
+
97
+ def extension(extension)
98
+ annotate_next_task_single(:extension, extension)
99
+ end
100
+
63
101
  def task(name_and_type, &block)
64
102
  name, type = name_and_type.collect.first
65
103
  @tasks ||= IndiferentHash.setup({})
66
104
  begin
67
105
  @annotate_next_task ||= {}
68
- task = Task.setup(block, @annotate_next_task.merge(name: name, type: type, directory: directory[name]))
106
+ task = Task.setup(block, @annotate_next_task.merge(name: name, type: type, directory: directory[name], workflow: self))
69
107
  @tasks[name] = task
70
108
  ensure
71
109
  @annotate_next_task = {}
72
110
  end
73
111
  end
74
112
 
75
- def desc(description)
76
- annotate_next_task_single(:description, description)
113
+ def task_alias(name, workflow, oname, *rest, &block)
114
+ dep(workflow, oname, *rest, &block)
115
+ extension :dep_task unless @extension
116
+ returns workflow.tasks[oname].returns if workflow.tasks.include?(oname) unless @returns
117
+ task name => nil do
118
+ raise RbbtException, "dep_task does not have any dependencies" if dependencies.empty?
119
+ Step.wait_for_jobs dependencies.select{|d| d.streaming? }
120
+ dep = dependencies.last
121
+ dep.join
122
+ raise dep.get_exception if dep.error?
123
+ raise Aborted, "Aborted dependency #{dep.path}" if dep.aborted?
124
+ set_info :result_type, dep.info[:result_type]
125
+ forget = config :forget_dep_tasks, "forget_dep_tasks", :default => FORGET_DEP_TASKS
126
+ if forget
127
+ remove = config :remove_dep_tasks, "remove_dep_tasks", :default => REMOVE_DEP_TASKS
128
+
129
+ self.archive_deps
130
+ self.copy_files_dir
131
+ self.dependencies = self.dependencies - [dep]
132
+ Open.rm_rf self.files_dir if Open.exist? self.files_dir
133
+ FileUtils.cp_r dep.files_dir, self.files_dir if Open.exist?(dep.files_dir)
134
+
135
+ if dep.overriden || ! Workflow.job_path?(dep.path)
136
+ Open.link dep.path, self.tmp_path
137
+ else
138
+ Open.ln_h dep.path, self.tmp_path
139
+
140
+ case remove.to_s
141
+ when 'true'
142
+ dep.clean
143
+ when 'recursive'
144
+ (dep.dependencies + dep.rec_dependencies).uniq.each do |d|
145
+ next if d.overriden
146
+ d.clean unless config(:remove_dep, d.task_signature, d.task_name, d.workflow.to_s, :default => true).to_s == 'false'
147
+ end
148
+ dep.clean unless config(:remove_dep, dep.task_signature, dep.task_name, dep.workflow.to_s, :default => true).to_s == 'false'
149
+ end
150
+ end
151
+ else
152
+ if Open.exists?(dep.files_dir)
153
+ Open.rm_rf self.files_dir
154
+ Open.link dep.files_dir, self.files_dir
155
+ end
156
+ if defined?(RemoteStep) && RemoteStep === dep
157
+ Open.write(self.tmp_path, Open.read(dep.path))
158
+ else
159
+ Open.link dep.path, self.path
160
+ end
161
+ end
162
+ nil
163
+ end
77
164
  end
165
+
166
+ alias dep_task task_alias
167
+
168
+ def export(*args)
169
+ end
170
+
171
+ alias export_synchronous export
172
+ alias export_asynchronous export
173
+ alias export_exec export
174
+ alias export_stream export
78
175
  end
@@ -0,0 +1,18 @@
1
+ require_relative '../../config'
2
+
3
+ class Step
4
+ def config(key, *tokens)
5
+ options = tokens.pop if Hash === tokens.last
6
+ options ||= {}
7
+
8
+ new_tokens = []
9
+ if workflow
10
+ workflow_name = workflow.to_s
11
+ new_tokens << ("workflow:" << workflow_name)
12
+ new_tokens << ("task:" << workflow_name << "#" << task_name.to_s)
13
+ end
14
+ new_tokens << ("task:" << task_name.to_s)
15
+
16
+ Scout::Config.get(key, tokens + new_tokens, options)
17
+ end
18
+ end
@@ -0,0 +1,40 @@
1
+ class Step
2
+ def recursive_inputs
3
+ dependencies.inject(@inputs.annotate(@inputs.dup)) do |acc,dep|
4
+ acc.concat(dep.inputs) if dep.inputs
5
+ acc
6
+ end
7
+ end
8
+
9
+ def input_dependencies
10
+ return [] unless inputs
11
+ inputs.select do |d|
12
+ Step === d
13
+ end
14
+ end
15
+
16
+ def prepare_dependencies
17
+ inverse_dep = {}
18
+ dependencies.each{|dep|
19
+ next if dep.done?
20
+ if dep.dependencies
21
+ dep.dependencies.each do |d|
22
+ inverse_dep[d] ||= []
23
+ inverse_dep[d] << dep
24
+ end
25
+ end
26
+ input_dependencies.each do |d|
27
+ inverse_dep[d] ||= []
28
+ inverse_dep[d] << dep
29
+ end
30
+ }
31
+ inverse_dep.each do |dep,list|
32
+ dep.tee_copies = list.length
33
+ end
34
+ end
35
+
36
+ def run_dependencies
37
+ dependencies.each{|dep| dep.run unless dep.running? || dep.done? }
38
+ end
39
+
40
+ end
@@ -0,0 +1,15 @@
1
+ class Step
2
+ def files_dir
3
+ @files_dir ||= begin
4
+ dir = @path + ".files"
5
+ @path.annotate(dir) if Path === @path
6
+ dir
7
+ end
8
+ end
9
+
10
+ def file(file)
11
+ dir = files_dir
12
+ Path.setup(dir) unless Path === dir
13
+ dir[file]
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  class Step
2
- SERIALIZER = :json
2
+ SERIALIZER = :marshal
3
3
  def info_file
4
4
  @info_file ||= begin
5
5
  info_file = @path + ".info"
@@ -20,7 +20,11 @@ class Step
20
20
  end
21
21
 
22
22
  def info
23
- outdated = @info && Open.exists?(info_file) && @info_load_time && Open.mtime(info_file) > @info_load_time
23
+ outdated = begin
24
+ @info_load_time && (mtime = Open.mtime(info_file)) && mtime > @info_load_time
25
+ rescue
26
+ true
27
+ end
24
28
 
25
29
  if @info.nil? || outdated
26
30
  load_info
@@ -33,6 +37,19 @@ class Step
33
37
  info = self.info
34
38
  new_info.each do |key,value|
35
39
  report_status new_info[:status], new_info[:message] if key == :status
40
+ if Exception === value
41
+ begin
42
+ Marshal.dump(value)
43
+ rescue TypeError
44
+ if ScoutException === value
45
+ new = ScoutException.new value.message
46
+ else
47
+ new = Exception.new value.message
48
+ end
49
+ new.set_backtrace(value.backtrace)
50
+ value = new
51
+ end
52
+ end
36
53
  if info.include?(key)
37
54
  case info[key]
38
55
  when Array
@@ -62,7 +79,6 @@ class Step
62
79
  end
63
80
 
64
81
  def log(status, message = nil)
65
- report_status status, message
66
82
  if message
67
83
  merge_info :status => status, :messages => [message]
68
84
  else
@@ -71,7 +87,22 @@ class Step
71
87
  end
72
88
 
73
89
  def status
74
- info[:status]
90
+ info[:status].tap{|s| s.nil? ? s : s.to_sym }
75
91
  end
76
92
 
93
+ def error?
94
+ status == :error
95
+ end
96
+
97
+ def aborted?
98
+ status == :aborted
99
+ end
100
+
101
+ def running?
102
+ ! done? && (info[:pid] && Misc.pid_alive?(info[:pid]))
103
+ end
104
+
105
+ def exception
106
+ info[:exception]
107
+ end
77
108
  end
@@ -0,0 +1,14 @@
1
+ class Step
2
+ def progress_bar(msg = "Progress", options = nil)
3
+ if Hash === msg and options.nil?
4
+ options = msg
5
+ msg = nil
6
+ end
7
+ options = {} if options.nil?
8
+
9
+ max = options[:max]
10
+ Open.mkdir files_dir
11
+ Log::ProgressBar.new_bar(max, {:desc => msg, :file => (@exec ? nil : file(:progress))}.merge(options))
12
+ end
13
+ end
14
+
@@ -0,0 +1,148 @@
1
+ class Step
2
+ def self.job_path?(path)
3
+ path.split("/")[-4] == "jobs"
4
+ end
5
+
6
+ def self.status_color(status)
7
+ case status.to_sym
8
+ when :error, :aborted, :missing, :dead, :unsync
9
+ :red
10
+ when :streaming, :started
11
+ :cyan
12
+ when :done, :noinfo
13
+ :green
14
+ when :dependencies, :waiting, :setup
15
+ :yellow
16
+ when :notfound, :cleaned
17
+ :blue
18
+ else
19
+ if status.to_s.index ">"
20
+ :cyan
21
+ else
22
+ :cyan
23
+ end
24
+ end
25
+ end
26
+
27
+ def self.prov_status_msg(status)
28
+ color = status_color(status)
29
+ Log.color(color, status.to_s)
30
+ end
31
+
32
+ def self.prov_report_msg(status, name, path, info, input = nil)
33
+ parts = path.sub(/\{.*/,'').split "/"
34
+
35
+ parts.pop
36
+
37
+ task = Log.color(:yellow, parts.pop)
38
+ workflow = Log.color(:magenta, parts.pop)
39
+
40
+ if ! Step.job_path?(path)
41
+ task, status, workflow = Log.color(:yellow, info[:task_name]), Log.color(:green, "file"), Log.color(:magenta, "-")
42
+ end
43
+
44
+ path_mtime = begin
45
+ Open.mtime(path)
46
+ rescue Exception
47
+ nil
48
+ end
49
+
50
+ if input.nil? || input.empty?
51
+ input_str = nil
52
+ else
53
+ input = input.reject{|dep,name| (input & dep.dependencies.collect{|d| [d,name]}).any? }
54
+ input = input.reject{|dep,name| (input & dep.input_dependencies.collect{|d| [d,name]}).any? }
55
+ input_str = Log.color(:magenta, "-> ") + input.collect{|dep,name| Log.color(:yellow, dep.task_name.to_s) + ":" + Log.color(:yellow, name) }.uniq * " "
56
+ end
57
+
58
+ str = if ! (Open.remote?(path) || Open.ssh?(path)) && (Open.exists?(path) && $main_mtime && path_mtime && ($main_mtime - path_mtime) < -2)
59
+ prov_status_msg(status.to_s) << " " << [workflow, task, path, input_str].compact * " " << " (#{Log.color(:red, "Mtime out of sync") })"
60
+ else
61
+ prov_status_msg(status.to_s) << " " << [workflow, task, path, input_str].compact * " "
62
+ end
63
+
64
+ if $inputs and $inputs.any?
65
+ job_inputs = Workflow.load_step(path).recursive_inputs.to_hash
66
+ IndiferentHash.setup(job_inputs)
67
+
68
+ $inputs.each do |input|
69
+ value = job_inputs[input]
70
+ next if value.nil?
71
+ value_str = Misc.fingerprint(value)
72
+ str << "\t#{Log.color :magenta, input}=#{value_str}"
73
+ end
74
+ end
75
+
76
+ if $info_fields and $info_fields.any?
77
+ $info_fields.each do |field|
78
+ IndiferentHash.setup(info)
79
+ value = info[field]
80
+ next if value.nil?
81
+ value_str = Misc.fingerprint(value)
82
+ str << "\t#{Log.color :magenta, field}=#{value_str}"
83
+ end
84
+ end
85
+
86
+ str << "\n"
87
+ end
88
+
89
+ def self.prov_report(step, offset = 0, task = nil, seen = [], expand_repeats = false, input = nil)
90
+ info = step.info || {}
91
+ info[:task_name] = task
92
+ path = step.path
93
+ status = info[:status] || :missing
94
+ status = "remote" if Open.remote?(path) || Open.ssh?(path)
95
+ name = info[:name] || File.basename(path)
96
+ status = :unsync if status == :done and not Open.exist?(path)
97
+ status = :notfound if status == :noinfo and not Open.exist?(path)
98
+
99
+
100
+ this_step_msg = prov_report_msg(status, name, path, info, input)
101
+
102
+ input_dependencies = {}
103
+ step.dependencies.each do |dep|
104
+ if dep.input_dependencies.any?
105
+ dep.input_dependencies.each do |id|
106
+ input_name, _dep = dep.recursive_inputs.fields.zip(dep.recursive_inputs).select{|f,d|
107
+ d == id || (String === d && d.start_with?(id.files_dir)) || (Array === d && d.include?(id))
108
+ }.last
109
+ if input_name
110
+ input_dependencies[id] ||= []
111
+ input_dependencies[id] << [dep, input_name]
112
+ end
113
+ end
114
+ end
115
+ end if step.dependencies
116
+
117
+ str = ""
118
+ str = " " * offset + this_step_msg if ENV["RBBT_ORIGINAL_STACK"] == 'true'
119
+
120
+ step.dependencies.dup.tap{|l|
121
+ l.reverse! if ENV["RBBT_ORIGINAL_STACK"] == 'true'
122
+ }.each do |dep|
123
+ path = dep.path
124
+ new = ! seen.include?(path)
125
+ if new
126
+ seen << path
127
+ str << prov_report(dep, offset + 1, task, seen, expand_repeats, input_dependencies[dep])
128
+ else
129
+ if expand_repeats
130
+ str << Log.color(Step.status_color(dep.status), Log.uncolor(prov_report(dep, offset+1, task)))
131
+ else
132
+ info = dep.info || {}
133
+ status = info[:status] || :missing
134
+ status = "remote" if Open.remote?(path) || Open.ssh?(path)
135
+ name = info[:name] || File.basename(path)
136
+ status = :unsync if status == :done and not Open.exist?(path)
137
+ status = :notfound if status == :noinfo and not Open.exist?(path)
138
+
139
+ str << Log.color(Step.status_color(status), " " * (offset + 1) + Log.uncolor(prov_report_msg(status, name, path, info, input_dependencies[dep])))
140
+ end
141
+ end
142
+ end if step.dependencies
143
+
144
+ str += (" " * offset) + this_step_msg unless ENV["RBBT_ORIGINAL_STACK"] == 'true'
145
+
146
+ str
147
+ end
148
+ end