dv-flow-mgr 0.0.2.14180132375a1__py3-none-any.whl → 1.0.0.14370600369a1__py3-none-any.whl

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 (43) hide show
  1. dv_flow/mgr/__init__.py +2 -1
  2. dv_flow/mgr/cmds/cmd_graph.py +2 -3
  3. dv_flow/mgr/cmds/cmd_run.py +7 -9
  4. dv_flow/mgr/cmds/cmd_show.py +1 -2
  5. dv_flow/mgr/cond_def.py +16 -0
  6. dv_flow/mgr/config.py +7 -0
  7. dv_flow/mgr/config_def.py +33 -0
  8. dv_flow/mgr/exec_callable.py +88 -0
  9. dv_flow/mgr/{pkg_rgy.py → ext_rgy.py} +44 -35
  10. dv_flow/mgr/extend_def.py +21 -0
  11. dv_flow/mgr/fragment_def.py +4 -3
  12. dv_flow/mgr/need_def.py +6 -0
  13. dv_flow/mgr/null_callable.py +10 -0
  14. dv_flow/mgr/package.py +30 -6
  15. dv_flow/mgr/package_def.py +40 -441
  16. dv_flow/mgr/package_loader.py +701 -0
  17. dv_flow/mgr/param_def.py +2 -1
  18. dv_flow/mgr/parser.out +567 -0
  19. dv_flow/mgr/pytask_callable.py +25 -0
  20. dv_flow/mgr/root_package.py +9 -0
  21. dv_flow/mgr/shell_callable.py +14 -0
  22. dv_flow/mgr/srcinfo.py +15 -0
  23. dv_flow/mgr/std/flow.dv +25 -4
  24. dv_flow/mgr/task.py +68 -0
  25. dv_flow/mgr/task_def.py +36 -24
  26. dv_flow/mgr/task_graph_builder.py +503 -240
  27. dv_flow/mgr/task_listener_log.py +4 -0
  28. dv_flow/mgr/task_node_ctor.py +11 -3
  29. dv_flow/mgr/task_node_ctor_compound.py +30 -34
  30. dv_flow/mgr/task_node_leaf.py +25 -3
  31. dv_flow/mgr/task_params_ctor.py +0 -1
  32. dv_flow/mgr/task_run_ctxt.py +4 -0
  33. dv_flow/mgr/task_runner.py +2 -0
  34. dv_flow/mgr/util/cmds/cmd_schema.py +0 -2
  35. dv_flow/mgr/util/util.py +4 -3
  36. dv_flow/mgr/yaml_srcinfo_loader.py +55 -0
  37. {dv_flow_mgr-0.0.2.14180132375a1.dist-info → dv_flow_mgr-1.0.0.14370600369a1.dist-info}/METADATA +1 -1
  38. dv_flow_mgr-1.0.0.14370600369a1.dist-info/RECORD +74 -0
  39. dv_flow_mgr-0.0.2.14180132375a1.dist-info/RECORD +0 -59
  40. {dv_flow_mgr-0.0.2.14180132375a1.dist-info → dv_flow_mgr-1.0.0.14370600369a1.dist-info}/WHEEL +0 -0
  41. {dv_flow_mgr-0.0.2.14180132375a1.dist-info → dv_flow_mgr-1.0.0.14370600369a1.dist-info}/entry_points.txt +0 -0
  42. {dv_flow_mgr-0.0.2.14180132375a1.dist-info → dv_flow_mgr-1.0.0.14370600369a1.dist-info}/licenses/LICENSE +0 -0
  43. {dv_flow_mgr-0.0.2.14180132375a1.dist-info → dv_flow_mgr-1.0.0.14370600369a1.dist-info}/top_level.txt +0 -0
@@ -22,14 +22,26 @@
22
22
  import os
23
23
  import dataclasses as dc
24
24
  import logging
25
- from typing import Callable
25
+ from typing import Callable, Any, Dict, List, Union
26
26
  from .package import Package
27
27
  from .package_def import PackageDef, PackageSpec
28
- from .pkg_rgy import PkgRgy
28
+ from .ext_rgy import ExtRgy
29
+ from .task import Task
29
30
  from .task_def import RundirE
31
+ from .task_data import TaskMarker, TaskMarkerLoc, SeverityE
30
32
  from .task_node import TaskNode
31
33
  from .task_node_ctor import TaskNodeCtor
32
- from typing import Dict, List, Union
34
+ from .task_node_ctor_compound import TaskNodeCtorCompound
35
+ from .task_node_ctor_compound_proxy import TaskNodeCtorCompoundProxy
36
+ from .task_node_ctor_proxy import TaskNodeCtorProxy
37
+ from .task_node_ctor_task import TaskNodeCtorTask
38
+ from .task_node_ctor_wrapper import TaskNodeCtorWrapper
39
+ from .task_node_compound import TaskNodeCompound
40
+ from .task_node_leaf import TaskNodeLeaf
41
+ from .std.task_null import TaskNull
42
+ from .exec_callable import ExecCallable
43
+ from .null_callable import NullCallable
44
+ from .shell_callable import ShellCallable
33
45
 
34
46
  @dc.dataclass
35
47
  class TaskNamespaceScope(object):
@@ -46,85 +58,68 @@ class CompoundTaskCtxt(object):
46
58
  @dc.dataclass
47
59
  class TaskGraphBuilder(object):
48
60
  """The Task-Graph Builder knows how to discover packages and construct task graphs"""
49
- root_pkg : PackageDef
61
+ root_pkg : Package
50
62
  rundir : str
51
- pkg_rgy : PkgRgy = None
52
63
  marker_l : Callable = lambda *args, **kwargs: None
53
- _pkg_s : List[Package] = dc.field(default_factory=list)
54
64
  _pkg_m : Dict[PackageSpec,Package] = dc.field(default_factory=dict)
55
65
  _pkg_spec_s : List[PackageDef] = dc.field(default_factory=list)
56
- _task_m : Dict['TaskSpec',TaskNode] = dc.field(default_factory=dict)
66
+ _shell_m : Dict[str,Callable] = dc.field(default_factory=dict)
67
+ _task_m : Dict[str,Task] = dc.field(default_factory=dict)
68
+ _task_node_m : Dict['TaskSpec',TaskNode] = dc.field(default_factory=dict)
69
+ _task_ctor_m : Dict[Task,TaskNodeCtor] = dc.field(default_factory=dict)
57
70
  _override_m : Dict[str,str] = dc.field(default_factory=dict)
58
71
  _ns_scope_s : List[TaskNamespaceScope] = dc.field(default_factory=list)
59
72
  _compound_task_ctxt_s : List[CompoundTaskCtxt] = dc.field(default_factory=list)
60
- _rundir_s : List[str] = dc.field(default_factory=list)
73
+ _task_rundir_s : List[List[str]] = dc.field(default_factory=list)
61
74
  _uses_count : int = 0
62
75
 
63
- _logger : logging.Logger = None
76
+ _log : logging.Logger = None
64
77
 
65
78
  def __post_init__(self):
66
- if self.pkg_rgy is None:
67
- self.pkg_rgy = PkgRgy.inst().copy()
68
-
69
79
  # Initialize the overrides from the global registry
70
- self._override_m.update(self.pkg_rgy.getOverrides())
71
-
72
- self._logger = logging.getLogger(type(self).__name__)
80
+ self._log = logging.getLogger(type(self).__name__)
81
+ self._shell_m.update(ExtRgy._inst._shell_m)
82
+ self._task_rundir_s.append([])
73
83
 
74
84
  if self.root_pkg is not None:
75
- self._logger.debug("TaskGraphBuilder: root_pkg: %s" % str(self.root_pkg))
76
-
77
- # Register package definitions found during loading
78
- visited = set()
79
- self._registerPackages(self.root_pkg, visited)
80
-
81
- self._pkg_spec_s.append(self.root_pkg)
82
- pkg = self.root_pkg.mkPackage(self)
83
- self._pkg_spec_s.pop()
84
-
85
- # Allows us to find ourselves
86
- self._pkg_m[PackageSpec(self.root_pkg.name)] = pkg
87
-
88
- def loadPkg(self, pkgfile : str):
89
- pkg = PackageDef.load(pkgfile)
90
- visited = set()
91
- self._registerPackages(pkg, visited)
85
+ # Collect all the tasks
86
+ pkg_s = set()
87
+ self._addPackageTasks(self.root_pkg, pkg_s)
88
+
89
+ def _addPackageTasks(self, pkg, pkg_s):
90
+ if pkg not in pkg_s:
91
+ pkg_s.add(pkg)
92
+ for task in pkg.task_m.values():
93
+ self._addTask(task)
94
+ for subpkg in pkg.pkg_m.values():
95
+ self._addPackageTasks(subpkg, pkg_s)
96
+
97
+ def _addTask(self, task):
98
+ if task.name not in self._task_m.keys():
99
+ self._task_m[task.name] = task
100
+ for st in task.subtasks:
101
+ self._addTask(st)
92
102
 
93
103
  def addOverride(self, key : str, val : str):
94
104
  self._override_m[key] = val
95
105
 
96
- def _registerPackages(self, pkg : PackageDef, visited):
97
- self._logger.debug("Packages: %s" % str(pkg))
98
- if pkg.name not in visited:
99
- visited.add(pkg.name)
100
- self._logger.debug("Registering package %s" % pkg.name)
101
- self.pkg_rgy.registerPackage(pkg)
102
- for subpkg in pkg.subpkg_m.values():
103
- self._registerPackages(subpkg, visited)
104
-
105
-
106
- def push_package(self, pkg : Package, add=False):
107
- self._pkg_s.append(pkg)
108
- if add:
109
- self._pkg_m[PackageSpec(pkg.name, pkg.params)] = pkg
110
-
111
- def pop_package(self, pkg : Package):
112
- self._pkg_s.pop()
113
-
114
- def package(self):
115
- return self._pkg_s[-1]
106
+ def enter_package(self, pkg : PackageDef):
107
+ pass
116
108
 
117
109
  def enter_rundir(self, rundir : str):
118
- self._rundir_s.append(rundir)
110
+ self._log.debug("enter_rundir: %s (%d)" % (rundir, len(self._task_rundir_s[-1])))
111
+ self._task_rundir_s[-1].append(rundir)
119
112
 
120
113
  def get_rundir(self, rundir=None):
121
- ret = self._rundir_s.copy()
114
+ ret = self._task_rundir_s[-1].copy()
122
115
  if rundir is not None:
123
116
  ret.append(rundir)
117
+ self._log.debug("get_rundir: %s" % str(ret))
124
118
  return ret
125
119
 
126
120
  def leave_rundir(self):
127
- self._rundir_s.pop()
121
+ self._log.debug("leave_rundir")
122
+ self._task_rundir_s[-1].pop()
128
123
 
129
124
  def enter_uses(self):
130
125
  self._uses_count += 1
@@ -142,15 +137,6 @@ class TaskGraphBuilder(object):
142
137
  if rundir is None or rundir == RundirE.Unique:
143
138
  self._rundir_s.append(task.name)
144
139
 
145
- def get_name_prefix(self):
146
- if len(self._compound_task_ctxt_s) > 0:
147
- # Use the compound scope name
148
- name = ".".join(c.task.name for c in self._compound_task_ctxt_s)
149
- else:
150
- name = self._pkg_s[-1].name
151
-
152
- return name
153
-
154
140
  def enter_compound_uses(self):
155
141
  self._compound_task_ctxt_s[-1].uses_s.append({})
156
142
 
@@ -159,7 +145,7 @@ class TaskGraphBuilder(object):
159
145
  # Propagate the items up the stack, appending 'super' to
160
146
  # the names
161
147
  for k,v in self._compound_task_ctxt_s[-1].uses_s[-1].items():
162
- self._compound_task_ctxt_s[-1].uses[-2]["super.%s" % k] = v
148
+ self._compound_task_ctxt_s[-1].uses_s[-2]["super.%s" % k] = v
163
149
  else:
164
150
  # Propagate the items to the compound namespace, appending
165
151
  # 'super' to the names
@@ -171,18 +157,18 @@ class TaskGraphBuilder(object):
171
157
  return len(self._compound_task_ctxt_s) > 0 and len(self._compound_task_ctxt_s[-1].uses_s) != 0
172
158
 
173
159
  def addTask(self, name, task : TaskNode):
174
- self._logger.debug("--> addTask: %s" % name)
160
+ self._log.debug("--> addTask: %s" % name)
175
161
 
176
162
  if len(self._compound_task_ctxt_s) == 0:
177
- self._task_m[name] = task
163
+ self._task_node_m[name] = task
178
164
  else:
179
165
  if len(self._compound_task_ctxt_s[-1].uses_s) > 0:
180
166
  self._compound_task_ctxt_s[-1].uses_s[-1][name] = task
181
167
  else:
182
168
  self._compound_task_ctxt_s[-1].task_m[name] = task
183
- self._logger.debug("<-- addTask: %s" % name)
169
+ self._log.debug("<-- addTask: %s" % name)
184
170
 
185
- def findTask(self, name):
171
+ def findTask(self, name, create=True):
186
172
  task = None
187
173
 
188
174
  if len(self._compound_task_ctxt_s) > 0:
@@ -191,8 +177,22 @@ class TaskGraphBuilder(object):
191
177
  task = self._compound_task_ctxt_s[-1].uses_s[-1][name]
192
178
  if task is None and name in self._compound_task_ctxt_s[-1].task_m.keys():
193
179
  task = self._compound_task_ctxt_s[-1].task_m[name]
194
- if task is None and name in self._task_m.keys():
195
- task = self._task_m[name]
180
+ if task is None and name in self._task_node_m.keys():
181
+ task = self._task_node_m[name]
182
+
183
+ if task is None and create:
184
+ if name in self.root_pkg.task_m.keys():
185
+ task = self.mkTaskGraph(name)
186
+ self._log.debug("Found task %s in root package" % name)
187
+ else:
188
+ raise Exception("Failed to find task %s" % name)
189
+ pass
190
+ # Go search type definitions
191
+ pass
192
+
193
+ # Check the current package
194
+ # if len(self._pkg_s) > 0 and name in self._pkg_s[-1].task_m.keys():
195
+ # task = self._pkg_s[-1].task_m[name]
196
196
 
197
197
  return task
198
198
 
@@ -202,220 +202,483 @@ class TaskGraphBuilder(object):
202
202
  self._rundir_s.pop()
203
203
 
204
204
  def mkTaskGraph(self, task : str, rundir=None) -> TaskNode:
205
- self._pkg_s.clear()
206
- self._task_m.clear()
205
+ return self.mkTaskNode(task, rundir=rundir)
206
+ # self._task_node_m.clear()
207
207
 
208
- if rundir is not None:
209
- self._rundir_s.append(rundir)
208
+ # if rundir is not None:
209
+ # self._rundir_s.append(rundir)
210
210
 
211
- ret = self._mkTaskGraph(task)
211
+ # ret = self._mkTaskGraph(task)
212
212
 
213
- if rundir is not None:
214
- self._rundir_s.pop()
213
+ # if rundir is not None:
214
+ # self._rundir_s.pop()
215
215
 
216
- return ret
216
+ # return ret
217
217
 
218
218
  def _mkTaskGraph(self, task : str) -> TaskNode:
219
-
220
- elems = task.split(".")
221
-
222
- pkg_name = ".".join(elems[0:-1])
223
- task_name = elems[-1]
224
-
225
- if pkg_name == "":
226
- if len(self._pkg_spec_s) == 0:
227
- raise Exception("No package context for %s" % task)
228
- pkg_spec = self._pkg_spec_s[-1]
229
- pkg_name = pkg_spec.name
219
+ if task in self.root_pkg.task_m.keys():
220
+ task_t = self.root_pkg.task_m[task]
230
221
  else:
231
- pkg_spec = PackageSpec(pkg_name)
222
+ pass
232
223
 
233
- self._logger.debug("pkg_spec: %s" % str(pkg_spec))
234
- self._pkg_spec_s.append(pkg_spec)
235
- pkg = self.getPackage(pkg_spec)
236
-
237
- self._pkg_s.append(pkg)
224
+ if task_t is None:
225
+ raise Exception("Failed to find task %s" % task)
238
226
 
239
- ctor_t : TaskNodeCtor = pkg.getTaskCtor(task_name)
227
+ ctor = self._getTaskCtor(task_t)
240
228
 
241
- self._logger.debug("ctor_t: %s" % ctor_t.name)
229
+ params = ctor.mkTaskParams()
242
230
 
243
231
  needs = []
244
232
 
245
- for need_def in ctor_t.getNeeds():
246
- # Resolve the full name of the need
247
- need_fullname = self._resolveNeedRef(need_def)
248
- self._logger.debug("Searching for qualifed-name task %s" % need_fullname)
249
- if not need_fullname in self._task_m.keys():
250
- # Go back to the root from a rundir perspective
251
- rundir_s = self._rundir_s
252
- self._rundir_s = [need_fullname]
253
- need_t = self._mkTaskGraph(need_fullname)
254
- self._rundir_s = rundir_s
255
- self._task_m[need_fullname] = need_t
256
- needs.append(self._task_m[need_fullname])
257
-
258
- # The returned task should have all param references resolved
259
- params = ctor_t.mkTaskParams()
260
-
261
- if params is None:
262
- raise Exception("ctor %s returned None for params" % str(ctor_t))
263
-
264
- task = ctor_t.mkTaskNode(
233
+ for need in task_t.needs:
234
+ need_n = self.findTask(need.name)
235
+ if need_n is None:
236
+ raise Exception("Failed to find need %s" % need.name)
237
+ needs.append(need_n)
238
+
239
+ task = ctor.mkTaskNode(
265
240
  builder=self,
266
241
  params=params,
267
242
  name=task,
268
243
  needs=needs)
244
+ task.rundir = self.get_rundir(task.name)
269
245
  # task.rundir = rundir
270
246
 
271
- self._task_m[task.name] = task
272
-
273
- self._pkg_s.pop()
274
- self._pkg_spec_s.pop()
247
+ self._task_node_m[task.name] = task
275
248
 
276
249
  return task
250
+
251
+ def mkTaskNode(self, task_t, name=None, srcdir=None, needs=None, **kwargs):
252
+ self._log.debug("--> mkTaskNode: %s" % task_t)
277
253
 
278
- def _resolveNeedRef(self, need_def) -> str:
279
- if need_def.find(".") == -1:
280
- # Need is a local task. Prefix to avoid ambiguity
281
- return self._pkg_s[-1].name + "." + need_def
254
+ if task_t in self._task_m.keys():
255
+ task = self._task_m[task_t]
282
256
  else:
283
- return need_def
284
-
285
- def getPackage(self, spec : PackageSpec) -> Package:
286
- # Obtain the active package definition
287
- self._logger.debug("--> getPackage: %s len: %d" % (spec.name, len(self._pkg_spec_s)))
288
- if len(self._pkg_spec_s) > 0:
289
- pkg_spec = self._pkg_spec_s[-1]
290
- if self.root_pkg is not None and self.root_pkg.name == pkg_spec.name:
291
- pkg_def = self.root_pkg
257
+ raise Exception("task_t (%s) not present" % str(task_t))
258
+
259
+ ret = self._mkTaskNode(task)
260
+
261
+ if needs is not None:
262
+ for need in needs:
263
+ ret.needs.append((need, False))
264
+
265
+ for k,v in kwargs.items():
266
+ if hasattr(ret.params, k):
267
+ setattr(ret.params, k, v)
292
268
  else:
293
- pkg_def = self.pkg_rgy.getPackage(pkg_spec.name)
269
+ raise Exception("Task %s parameters do not include %s" % (task.name, k))
270
+
271
+ self._log.debug("<-- mkTaskNode: %s" % task_t)
272
+ return ret
273
+
274
+ def _findTask(self, pkg, name):
275
+ task = None
276
+ if name in pkg.task_m.keys():
277
+ task = pkg.task_m[name]
294
278
  else:
295
- pkg_def = None
296
-
297
- # Need a stack to track which package we are currently in
298
- # Need a map to get a concrete package from a name with parameterization
299
-
300
- self._logger.debug("pkg_s: %d %s" % (
301
- len(self._pkg_s), (self._pkg_s[-1].name if len(self._pkg_s) else "<unknown>")))
302
-
303
- # First, check the active pkg_def to see if any aliases
304
- # Should be considered
305
- pkg_name = spec.name
306
- if pkg_def is not None:
307
- # Look for an import alias
308
- self._logger.debug("Search package %s for import alias %s" % (
309
- pkg_def.name, pkg_spec.name))
310
- for imp in pkg_def.imports:
311
- if type(imp) != str:
312
- self._logger.debug("imp: %s" % str(imp))
313
- if imp.alias is not None and imp.alias == spec.name:
314
- # Found the alias name. Just need to get an instance of this package
315
- self._logger.debug("Found alias %s -> %s" % (imp.alias, imp.name))
316
- pkg_name = imp.name
317
- break
279
+ for subpkg in pkg.pkg_m.values():
280
+ task = self._findTask(subpkg, name)
281
+ if task is not None:
282
+ break
283
+ return task
284
+
285
+ def _mkTaskNode(self, task : Task, hierarchical=False):
286
+
287
+ if not hierarchical:
288
+ self._task_rundir_s.append([])
318
289
 
319
- # Note: _pkg_m needs to be context specific, such that imports from
320
- # one package don't end up visible in another
321
- spec.name = pkg_name
322
-
323
- if spec in self._pkg_m.keys():
324
- self._logger.debug("Found cached package instance")
325
- pkg = self._pkg_m[spec]
326
- elif self.pkg_rgy.hasPackage(spec.name):
327
- self._logger.debug("Registry has a definition")
328
- p_def = self.pkg_rgy.getPackage(spec.name)
329
-
330
- self._pkg_spec_s.append(p_def)
331
- pkg = p_def.mkPackage(self)
332
- self._pkg_spec_s.pop()
333
- self._pkg_m[spec] = pkg
290
+ # Determine how to build this node
291
+ if task.subtasks is not None and len(task.subtasks):
292
+ ret = self._mkTaskCompoundNode(task)
334
293
  else:
335
- self.error("Failed to find package %s" % spec.name)
336
- raise Exception("Failed to find definition of package %s" % spec.name)
294
+ ret = self._mkTaskLeafNode(task)
337
295
 
338
- self._logger.debug("<-- getPackage: %s" % str(pkg))
296
+ if not hierarchical:
297
+ self._task_rundir_s.pop()
339
298
 
340
- return pkg
299
+ return ret
341
300
 
342
- def mkTaskNode(self, task_t, name=None, srcdir=None, needs=None, **kwargs):
343
- self._logger.debug("--> mkTaskNode: %s" % task_t)
344
-
345
- if task_t in self._override_m.keys():
346
- self._logger.debug("Overriding task %s with %s" % (task_t, self._override_m[task_t]))
347
- task_t = self._override_m[task_t]
301
+ def _getTaskNode(self, name):
302
+ if name in self._task_node_m.keys():
303
+ return self._task_node_m[name]
348
304
  else:
349
- dot_idx = task_t.rfind(".")
350
- if dot_idx != -1:
351
- pkg = task_t[0:dot_idx]
352
- tname = task_t[dot_idx+1:]
353
-
354
- if pkg in self._override_m.keys():
355
- self._logger.debug("Overriding package %s with %s" % (pkg, self._override_m[pkg]))
356
- task_t = self._override_m[pkg] + "." + tname
357
-
358
- dot_idx = task_t.rfind(".")
359
- pkg = task_t[0:dot_idx]
360
- self._pkg_s.append(self.getPackage(PackageSpec(pkg)))
361
-
362
- ctor = self.getTaskCtor(task_t)
363
- if ctor is not None:
364
- if needs is None:
365
- needs = []
366
- for need_def in ctor.getNeeds():
367
- # Resolve the full name of the need
368
- need_fullname = self._resolveNeedRef(need_def)
369
- self._logger.debug("Searching for qualifed-name task %s" % need_fullname)
370
- if not need_fullname in self._task_m.keys():
371
- rundir_s = self._rundir_s
372
- self._rundir_s = [need_fullname]
373
- need_t = self._mkTaskGraph(need_fullname)
374
- self._rundir_s = rundir_s
375
- self._task_m[need_fullname] = need_t
376
- needs.append(self._task_m[need_fullname])
377
-
378
- self._logger.debug("ctor: %s" % ctor.name)
379
- params = ctor.mkTaskParams(kwargs)
380
- ret = ctor.mkTaskNode(
381
- self,
382
- params=params,
383
- name=name,
384
- srcdir=srcdir,
385
- needs=needs)
386
- ret.rundir = self.get_rundir(name)
305
+ return self.mkTaskNode(name)
306
+
307
+ def _mkTaskLeafNode(self, task : Task, name=None) -> TaskNode:
308
+ self._log.debug("--> _mkTaskLeafNode %s" % task.name)
309
+ srcdir = os.path.dirname(task.srcinfo.file)
310
+
311
+ if task.rundir == RundirE.Unique:
312
+ self.enter_rundir(task.name)
313
+
314
+ if name is None:
315
+ name = task.name
316
+
317
+ callable = None
318
+ if task.run is not None:
319
+ shell = task.shell if task.shell is not None else "shell"
320
+ if shell in self._shell_m.keys():
321
+ self._log.debug("Use shell implementation")
322
+ callable = self._shell_m[shell]
323
+ else:
324
+ raise Exception("Shell %s not found" % shell)
387
325
  else:
388
- raise Exception("Failed to find ctor for task %s" % task_t)
389
- self._pkg_s.pop()
390
- self._logger.debug("<-- mkTaskNode: %s" % task_t)
391
- return ret
326
+ callable = NullCallable
327
+
328
+ node = TaskNodeLeaf(
329
+ name=name,
330
+ srcdir=srcdir,
331
+ params=task.paramT(),
332
+ passthrough=task.passthrough,
333
+ consumes=task.consumes,
334
+ task=callable(task.run))
335
+ self._task_node_m[name] = node
336
+ node.rundir = self.get_rundir()
337
+
338
+ # Now, link up the needs
339
+ self._log.debug("--> processing needs")
340
+ for n in task.needs:
341
+ self._log.debug("-- need %s" % n.name)
342
+ nn = self._getTaskNode(n.name)
343
+ node.needs.append((nn, False))
344
+ self._log.debug("<-- processing needs")
345
+
346
+ if task.rundir == RundirE.Unique:
347
+ self.leave_rundir()
348
+
349
+ self._log.debug("<-- _mkTaskLeafNode %s" % task.name)
350
+ return node
351
+
352
+ def _mkTaskCompoundNode(self, task : Task, name=None) -> TaskNode:
353
+ self._log.debug("--> _mkTaskCompoundNode %s" % task.name)
354
+ srcdir = os.path.dirname(task.srcinfo.file)
355
+
356
+ if name is None:
357
+ name = task.name
358
+
359
+ if task.rundir == RundirE.Unique:
360
+ self.enter_rundir(task.name)
361
+
362
+ # Node represents the terminal node of the sub-DAG
363
+ node = TaskNodeCompound(
364
+ name=name,
365
+ srcdir=srcdir,
366
+ params=task.paramT()
367
+ )
368
+ self._task_node_m[name] = node
369
+
370
+ node.rundir = self.get_rundir()
371
+
372
+ # Put the input node inside the compound task's rundir
373
+ self.enter_rundir(task.name + ".in")
374
+ node.input.rundir = self.get_rundir()
375
+ self.leave_rundir()
376
+
377
+ self._log.debug("--> processing needs")
378
+ for need in task.needs:
379
+ self._log.debug("-- need: %s" % need.name)
380
+ nn = self._getTaskNode(need.name)
381
+ node.input.needs.append((nn, False))
382
+ self._log.debug("<-- processing needs")
383
+
384
+ # TODO: handle strategy
385
+
386
+ # Need a local map of name -> task
387
+ # For now, build out local tasks and link up the needs
388
+ tasks = []
389
+ for t in task.subtasks:
390
+ nn = self._mkTaskNode(t, True)
391
+ tasks.append((t, self._getTaskNode(t.name)))
392
+
393
+ # Fill in 'needs'
394
+ for t, tn in tasks:
395
+
396
+ referenced = None
397
+ for tt in task.subtasks:
398
+ if tt in t.needs:
399
+ referenced = tt
400
+ break
401
+
402
+ refs_internal = None
403
+ for nn,_ in tn.needs:
404
+ for _,tnn in tasks:
405
+ if nn == tnn:
406
+ refs_internal = tnn
407
+ break
408
+ if refs_internal is not None:
409
+ break
410
+
411
+ if not refs_internal:
412
+ # Any node that doesn't depend on an internal
413
+ # task is a top-level task
414
+ self._log.debug("Node %s doesn't reference any internal node" % t.name)
415
+ tn.needs.append((node.input, False))
416
+ else:
417
+ self._log.debug("Node references internal node %s" % refs_internal.name)
418
+
419
+ if referenced is not None:
420
+ # Add this task as a dependency of the output
421
+ # node (the root one)
422
+ self._log.debug("Add node %s as a top-level dependency" % tn.name)
423
+ node.needs.append((tn, False))
424
+ else:
425
+ self._log.debug("Node %s has internal needs" % tn.name)
426
+
427
+ if task.rundir == RundirE.Unique:
428
+ self.leave_rundir()
429
+
430
+ return node
431
+
392
432
 
393
433
  def getTaskCtor(self, spec : Union[str,'TaskSpec'], pkg : PackageDef = None) -> 'TaskNodeCtor':
394
434
  from .task_def import TaskSpec
395
435
  if type(spec) == str:
396
436
  spec = TaskSpec(spec)
397
437
 
398
- self._logger.debug("--> getTaskCtor %s" % spec.name)
438
+ self._log.debug("--> getTaskCtor %s" % spec.name)
399
439
  spec_e = spec.name.split(".")
400
440
  task_name = spec_e[-1]
401
441
 
402
- if len(spec_e) == 1:
403
- # Just have a task name. Use the current package
404
- if len(self._pkg_s) == 0:
405
- raise Exception("No package context for task %s" % spec.name)
406
- pkg = self._pkg_s[-1]
407
- else:
408
- pkg_name = ".".join(spec_e[0:-1])
442
+ # if len(spec_e) == 1:
443
+ # # Just have a task name. Use the current package
444
+ # if len(self._pkg_s) == 0:
445
+ # raise Exception("No package context for task %s" % spec.name)
446
+ # pkg = self._pkg_s[-1]
447
+ # else:
448
+ # pkg_name = ".".join(spec_e[0:-1])
409
449
 
410
- try:
411
- pkg = self.getPackage(PackageSpec(pkg_name))
412
- except Exception as e:
413
- self._logger.critical("Failed to find package %s while looking for task %s" % (pkg_name, spec.name))
414
- raise e
450
+ # try:
451
+ # pkg = self.getPackage(PackageSpec(pkg_name))
452
+ # except Exception as e:
453
+ # self._log.critical("Failed to find package %s while looking for task %s" % (pkg_name, spec.name))
454
+ # raise e
415
455
 
416
456
  ctor = pkg.getTaskCtor(task_name)
417
457
 
418
- self._logger.debug("--> getTaskCtor %s" % spec.name)
458
+ self._log.debug("<-- getTaskCtor %s" % spec.name)
419
459
  return ctor
420
460
 
461
+ def error(self, msg, loc=None):
462
+ if loc is not None:
463
+ marker = TaskMarker(msg=msg, severity=SeverityE.Error, loc=loc)
464
+ else:
465
+ marker = TaskMarker(msg=msg, severity=SeverityE.Error)
466
+ self.marker(marker)
467
+
468
+ def marker(self, marker):
469
+ self.marker_l(marker)
470
+
471
+ def _getTaskCtor(self, task : Task) -> TaskNodeCtor:
472
+ if task in self._task_ctor_m.keys():
473
+ ctor = self._task_ctor_m[task]
474
+ else:
475
+ ctor = self._mkTaskCtor(task)
476
+ self._task_ctor_m[task] = ctor
477
+ return ctor
478
+
479
+ def _mkTaskCtor(self, task):
480
+ srcdir = os.path.dirname(task.srcinfo.file)
481
+ self._log.debug("--> mkTaskCtor %s (srcdir: %s)" % (task.name, srcdir))
482
+
483
+ if len(task.subtasks) > 0:
484
+ self._log.debug("Task has a body")
485
+ # Compound task
486
+ self._log.debug("Task specifies sub-task implementation")
487
+ ctor = self._mkCompoundTaskCtor(task)
488
+ else:
489
+ self._log.debug("Task doesn't specify a body")
490
+ # Shell task or 'null'
491
+ ctor = self._mkLeafTaskCtor(task)
492
+
493
+ if ctor is None:
494
+ raise Exception()
421
495
 
496
+ return ctor
497
+
498
+ def _mkLeafTaskCtor(self, task) -> TaskNodeCtor:
499
+ self._log.debug("--> _mkLeafTaskCtor")
500
+ srcdir = os.path.dirname(task.srcinfo.file)
501
+ base_ctor_t : TaskNodeCtor = None
502
+ ctor_t : TaskNodeCtor = None
503
+ base_params = None
504
+ callable = None
505
+ # fullname = self.name + "." + task.name
506
+ # rundir = task.rundir
507
+
508
+ # TODO: should we have the ctor look this up itself?
509
+ # Want to confirm that the value can be found.
510
+ # Defer final resolution until actual graph building (post-config)
511
+ if task.uses is not None:
512
+ self._log.debug("Uses: %s" % task.uses.name)
513
+
514
+ base_ctor_t = self._getTaskCtor(task.uses)
515
+
516
+ if base_ctor_t is None:
517
+ self._log.error("Failed to load task ctor %s" % task.uses)
518
+ # base_params = base_ctor_t.mkTaskParams()
519
+ else:
520
+ self._log.debug("No 'uses' specified %s" % task.name)
521
+
522
+ self._log.debug("%d needs" % len(task.needs))
523
+
524
+ # Determine the implementation constructor first
525
+ if task.run is not None:
526
+ shell = task.shell if task.shell is not None else "shell"
527
+
528
+ if shell in self._shell_m.keys():
529
+ self._log.debug("Use shell implementation")
530
+ callable = self._shell_m[shell]
531
+ else:
532
+ self._log.debug("Shell %s not found" % shell)
533
+ raise Exception("Shell %s not found" % shell)
534
+
535
+ # if taskdef.body.pytask is not None:
536
+ # # Built-in impl
537
+ # # Now, lookup the class
538
+ # self._log.debug("Use PyTask implementation")
539
+ # last_dot = taskdef.body.pytask.rfind('.')
540
+ # clsname = taskdef.body.pytask[last_dot+1:]
541
+ # modname = taskdef.body.pytask[:last_dot]
542
+
543
+ # try:
544
+ # if modname not in sys.modules:
545
+ # if srcdir not in sys.path:
546
+ # sys.path.append(srcdir)
547
+ # mod = importlib.import_module(modname)
548
+ # else:
549
+ # mod = sys.modules[modname]
550
+ # except ModuleNotFoundError as e:
551
+ # raise Exception("Failed to import module %s (_basedir=%s): %s" % (
552
+ # modname, self._basedir, str(e)))
553
+
554
+ # if not hasattr(mod, clsname):
555
+ # raise Exception("Method %s not found in module %s" % (clsname, modname))
556
+ # callable = getattr(mod, clsname)
557
+ # elif taskdef.body.run is not None:
558
+ # callable = self._getRunCallable(taskdef)
559
+ else:
560
+ # TODO: use null task
561
+ pass
562
+
563
+ # Determine if we need to use a new
564
+ if task.paramT is None:
565
+ raise Exception()
566
+ paramT = task.paramT
567
+ needs = []
568
+
569
+ # TODO:
570
+ rundir : RundirE = task.rundir
571
+
572
+ if callable is not None:
573
+ ctor_t = TaskNodeCtorTask(
574
+ name=task.name,
575
+ srcdir=srcdir,
576
+ paramT=task.paramT, # TODO: need to determine the parameter type
577
+ passthrough=task.passthrough,
578
+ consumes=task.consumes,
579
+ needs=needs, # TODO: need to determine the needs
580
+ rundir=rundir,
581
+ task=callable)
582
+ elif base_ctor_t is not None:
583
+ # Use the existing (base) to create the implementation
584
+ ctor_t = TaskNodeCtorProxy(
585
+ name=task.name,
586
+ srcdir=srcdir,
587
+ paramT=task.paramT, # TODO: need to determine the parameter type
588
+ passthrough=task.passthrough,
589
+ consumes=task.consumes,
590
+ needs=needs,
591
+ rundir=rundir,
592
+ uses=base_ctor_t)
593
+ else:
594
+ self._log.debug("Use 'Null' as the class implementation")
595
+ ctor_t = TaskNodeCtorTask(
596
+ name=task.name,
597
+ srcdir=srcdir,
598
+ paramT=paramT,
599
+ passthrough=task.passthrough,
600
+ consumes=task.consumes,
601
+ needs=needs,
602
+ rundir=rundir,
603
+ task=TaskNull)
604
+
605
+ self._log.debug("<-- mkTaskCtor %s" % task.name)
606
+ return ctor_t
607
+
608
+ def _getRunCallable(self, task):
609
+ self._log.debug("--> _getRunCallable %s" % task.name)
610
+ callable = None
611
+ if task.run is not None and task.shell == "python":
612
+ # Evaluate a Python script
613
+ pass
614
+ else:
615
+ # run a shell script
616
+ shell = None
617
+ body = task.run.strip()
618
+
619
+ callable = ShellCallable(body=body, shell=shell)
620
+ pass
621
+ return callable
622
+
623
+ def _mkCompoundTaskCtor(self, task) -> TaskNodeCtor:
624
+ self._log.debug("--> _mkCompoundTaskCtor %s" % task.name)
625
+ srcdir = os.path.dirname(task.srcinfo.file)
626
+ base_ctor_t : TaskNodeCtor = None
627
+ ctor_t : TaskNodeCtor = None
628
+ base_params = None
629
+ callable = None
630
+
631
+ # fullname = self._getScopeFullname()
632
+ fullname = task.name
633
+
634
+ if task.uses is not None:
635
+ self._log.debug("Uses: %s" % task.uses)
636
+ base_ctor_t = task.uses.ctor
637
+ base_params = base_ctor_t.mkTaskParams()
638
+
639
+ if base_ctor_t is None:
640
+ self._log.error("Failed to load task ctor %s" % task.uses)
641
+
642
+ # TODO: should build during loading
643
+ # passthrough, consumes, needs = self._getPTConsumesNeeds(taskdef, base_ctor_t)
644
+ passthrough = []
645
+ consumes = []
646
+ needs = []
647
+
648
+ # Determine if we need to use a new
649
+ # paramT = self._getParamT(taskdef, base_params)
650
+ paramT = task.paramT
651
+
652
+ if base_ctor_t is not None:
653
+ ctor_t = TaskNodeCtorCompoundProxy(
654
+ name=fullname,
655
+ srcdir=srcdir,
656
+ paramT=paramT,
657
+ passthrough=passthrough,
658
+ consumes=consumes,
659
+ needs=needs,
660
+ task=task,
661
+ uses=base_ctor_t)
662
+ else:
663
+ self._log.debug("No 'uses' specified")
664
+ ctor_t = TaskNodeCtorCompound(
665
+ name=fullname,
666
+ srcdir=srcdir,
667
+ paramT=paramT,
668
+ passthrough=passthrough,
669
+ consumes=consumes,
670
+ needs=needs,
671
+ task=task)
672
+
673
+ for st in task.subtasks:
674
+ ctor = self._getTaskCtor(st)
675
+ if ctor is None:
676
+ raise Exception("ctor for %s is None" % st.name)
677
+ ctor_t.tasks.append(st)
678
+
679
+ # for t in task.subtasks:
680
+ # ctor_t.tasks.append(self._mkTaskCtor(t, srcdir))
681
+
682
+
683
+ self._log.debug("<-- mkCompoundTaskCtor %s (%d)" % (task.name, len(ctor_t.tasks)))
684
+ return ctor_t