stepup 3.2.1__tar.gz → 3.2.2__tar.gz

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 (46) hide show
  1. {stepup-3.2.1/stepup.egg-info → stepup-3.2.2}/PKG-INFO +1 -1
  2. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/director.py +24 -1
  3. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/file.py +1 -1
  4. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/step.py +1 -1
  5. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/tui.py +11 -1
  6. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/worker.py +193 -153
  7. {stepup-3.2.1 → stepup-3.2.2/stepup.egg-info}/PKG-INFO +1 -1
  8. {stepup-3.2.1 → stepup-3.2.2}/LICENSE +0 -0
  9. {stepup-3.2.1 → stepup-3.2.2}/MANIFEST.in +0 -0
  10. {stepup-3.2.1 → stepup-3.2.2}/README.md +0 -0
  11. {stepup-3.2.1 → stepup-3.2.2}/pyproject.toml +0 -0
  12. {stepup-3.2.1 → stepup-3.2.2}/setup.cfg +0 -0
  13. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/__init__.py +0 -0
  14. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/__main__.py +0 -0
  15. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/actions.py +0 -0
  16. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/api.py +0 -0
  17. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/asyncio.py +0 -0
  18. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/browse.py +0 -0
  19. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/call.py +0 -0
  20. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/cascade.py +0 -0
  21. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/clean.py +0 -0
  22. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/deferred_glob.py +0 -0
  23. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/enums.py +0 -0
  24. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/exceptions.py +0 -0
  25. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/hash.py +0 -0
  26. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/interact.py +0 -0
  27. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/job.py +0 -0
  28. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/logo.svg +0 -0
  29. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/nglob.py +0 -0
  30. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/pytest.py +0 -0
  31. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/render_jinja.py +0 -0
  32. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/reporter.py +0 -0
  33. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/rpc.py +0 -0
  34. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/runner.py +0 -0
  35. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/scheduler.py +0 -0
  36. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/script.py +0 -0
  37. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/startup.py +0 -0
  38. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/stepinfo.py +0 -0
  39. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/utils.py +0 -0
  40. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/watcher.py +0 -0
  41. {stepup-3.2.1 → stepup-3.2.2}/stepup/core/workflow.py +0 -0
  42. {stepup-3.2.1 → stepup-3.2.2}/stepup.egg-info/SOURCES.txt +0 -0
  43. {stepup-3.2.1 → stepup-3.2.2}/stepup.egg-info/dependency_links.txt +0 -0
  44. {stepup-3.2.1 → stepup-3.2.2}/stepup.egg-info/entry_points.txt +0 -0
  45. {stepup-3.2.1 → stepup-3.2.2}/stepup.egg-info/requires.txt +0 -0
  46. {stepup-3.2.1 → stepup-3.2.2}/stepup.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stepup
3
- Version: 3.2.1
3
+ Version: 3.2.2
4
4
  Summary: StepUp Core provides the basic framework for the StepUp build tool
5
5
  Author-email: Toon Verstraelen <toon.verstraelen@ugent.be>
6
6
  License-Expression: GPL-3.0-or-later
@@ -34,7 +34,10 @@ from importlib.metadata import version as get_version
34
34
  import attrs
35
35
  from path import Path
36
36
 
37
- from stepup.core.step import Step
37
+ try:
38
+ import yappi
39
+ except ImportError:
40
+ yappi = None
38
41
 
39
42
  from .asyncio import wait_for_events
40
43
  from .enums import ReturnCode, StepState
@@ -46,6 +49,7 @@ from .rpc import allow_rpc, serve_socket_rpc
46
49
  from .runner import Runner
47
50
  from .scheduler import Scheduler
48
51
  from .startup import startup_from_db
52
+ from .step import Step
49
53
  from .stepinfo import StepInfo
50
54
  from .utils import DBLock, check_plan, mynormpath
51
55
  from .watcher import WATCHER_AVAILABLE, Watcher
@@ -71,6 +75,15 @@ async def async_main():
71
75
  print(f"SOCKET {args.director_socket}", file=sys.stderr)
72
76
  print(f"PID {os.getpid()}", file=sys.stderr)
73
77
  print(f"LOG_LEVEL {args.log_level}", file=sys.stderr)
78
+ if args.yappi:
79
+ if yappi is None:
80
+ print(
81
+ "Yappi profiling requested, but the yappi module is not installed.",
82
+ file=sys.stderr,
83
+ )
84
+ else:
85
+ yappi.set_clock_type("cpu")
86
+ yappi.start(builtins=True, profile_threads=True)
74
87
  async with ReporterClient.socket(args.reporter_socket) as reporter:
75
88
  num_workers = interpret_num_workers(args.num_workers)
76
89
  await reporter.set_num_workers(num_workers)
@@ -95,6 +108,10 @@ async def async_main():
95
108
  finally:
96
109
  await reporter("DIRECTOR", "See you!")
97
110
  await reporter.shutdown()
111
+ if args.yappi and yappi is not None:
112
+ yappi.stop()
113
+ stats = yappi.get_func_stats()
114
+ stats.save(".stepup/director.prof", type="pstat")
98
115
  sys.exit(returncode.value)
99
116
 
100
117
 
@@ -170,6 +187,12 @@ def parse_args() -> argparse.Namespace:
170
187
  action=argparse.BooleanOptionalAction,
171
188
  help="Do not remove outdated output files.",
172
189
  )
190
+ parser.add_argument(
191
+ "--yappi",
192
+ default=False,
193
+ action=argparse.BooleanOptionalAction,
194
+ help="Profile the director with Yappi (must be installed).",
195
+ )
173
196
  args = parser.parse_args()
174
197
  if WATCHER_AVAILABLE:
175
198
  if args.watch_first:
@@ -299,7 +299,7 @@ class File(Node):
299
299
  # Local import to avoid cyclic imports.
300
300
  from .step import Step # noqa: PLC0415
301
301
 
302
- for step in self.consumers(Step):
302
+ for step in self.consumers(Step, include_orphans=True):
303
303
  step.mark_pending()
304
304
  elif state != FileState.OUTDATED:
305
305
  raise ValueError(f"Cannot make file oudated when its state is {state}")
@@ -987,6 +987,6 @@ class Step(Node):
987
987
  logger.info("Mark %s step PENDING: %s", state.name, self.label)
988
988
  self.set_state(StepState.PENDING)
989
989
  # First make all consumers (output files) pending
990
- for file in self.consumers(File):
990
+ for file in self.consumers(File, include_orphans=True):
991
991
  if file.get_state() == FileState.BUILT:
992
992
  file.mark_outdated()
@@ -123,6 +123,8 @@ async def async_boot(args: argparse.Namespace):
123
123
  argv.append("--watch-first")
124
124
  if not args.clean:
125
125
  argv.append("--no-clean")
126
+ if args.yappi:
127
+ argv.append("--yappi")
126
128
  returncode = 1 # Internal error unless it is overriden later by the director subprocess
127
129
  try:
128
130
  with open(".stepup/director.log", "w") as log_file:
@@ -299,7 +301,15 @@ def boot_subcommand(subparsers) -> callable:
299
301
  default=os.getenv("STEPUP_PERF", None),
300
302
  nargs="?",
301
303
  const="500",
302
- help="Run the director under perf record, by default at a frequency of 500 Hz. "
304
+ help="Profile the director with perf, by default at a frequency of %(const)s Hz. "
303
305
  "(Only supported on Linux with perf installed.)",
304
306
  )
307
+ parser.add_argument(
308
+ "--yappi",
309
+ default=string_to_bool(os.getenv("STEPUP_YAPPI", "0")),
310
+ action=argparse.BooleanOptionalAction,
311
+ help="Profile the director with Yappi (must be installed). "
312
+ "This produces a .stepup/director.prof file that can be analyzed with "
313
+ "tools like SnakeViz.",
314
+ )
305
315
  return boot_tool
@@ -25,6 +25,7 @@ import contextlib
25
25
  import ctypes
26
26
  import inspect
27
27
  import io
28
+ import json
28
29
  import logging
29
30
  import os
30
31
  import queue
@@ -228,8 +229,6 @@ class WorkerClient:
228
229
  return True
229
230
 
230
231
  # Delegate the calculation of the output part of the step hash to the worker.
231
- # With skipping=True, the worker knows the outputs should not have changed
232
- # and will report it on screen.
233
232
  new_step_hash, new_out_hashes = await self.compute_out_step_hash(step, new_step_hash)
234
233
 
235
234
  if step_hash.out_digest != new_step_hash.out_digest:
@@ -707,6 +706,38 @@ def check_executable(executable: Path, shebang: str | None = None) -> bool:
707
706
  return True
708
707
 
709
708
 
709
+ @attrs.define
710
+ class Timer:
711
+ """Context managers to construct a JSON string with times spent in typical worker tasks."""
712
+
713
+ start: float = attrs.field(init=False, factory=perf_counter)
714
+ sections: dict[str, float] = attrs.field(init=False, factory=dict)
715
+
716
+ @contextlib.contextmanager
717
+ def section(self, section: str):
718
+ """Context manager for a timed section.
719
+
720
+ Parameters
721
+ ----------
722
+ section
723
+ The name of the section.
724
+ """
725
+ time = perf_counter()
726
+ try:
727
+ yield
728
+ finally:
729
+ time = perf_counter() - time
730
+ self.sections[section] = self.sections.get(section, 0.0) + time
731
+
732
+ def report(self):
733
+ """Write the timings line to standard error."""
734
+ time = perf_counter() - self.start
735
+ sections = self.sections.copy()
736
+ sections["idle/other"] = time - sum(sections.values())
737
+ sections["total"] = time
738
+ print(f"TIMINGS: {json.dumps(sections)}", file=sys.stderr)
739
+
740
+
710
741
  @attrs.define
711
742
  class WorkerHandler:
712
743
  """RPC Handler in the worker process to respond to requests from the WorkerClient."""
@@ -716,6 +747,7 @@ class WorkerHandler:
716
747
  explain_rerun: bool = attrs.field()
717
748
  stop_event: asyncio.Event = attrs.field(factory=asyncio.Event)
718
749
  step: WorkerStep | None = attrs.field(init=False, default=None)
750
+ timer: Timer = attrs.field(init=False, factory=Timer)
719
751
 
720
752
  @allow_rpc
721
753
  def shutdown(self):
@@ -777,18 +809,19 @@ class WorkerHandler:
777
809
  The new hash of the step, with the input part already computed, if available.
778
810
  `None` is yielded if, unexpectedly, some inputs are missing or have changed.
779
811
  """
780
- if self.step is not None:
781
- raise RPCError(
782
- "Worker cannot initiate two steps at the same time. "
783
- f"Still working on {self.step.action}"
784
- )
812
+ with self.timer.section("new_step"):
813
+ if self.step is not None:
814
+ raise RPCError(
815
+ "Worker cannot initiate two steps at the same time. "
816
+ f"Still working on {self.step.action}"
817
+ )
785
818
 
786
- # Create the step
787
- action, workdir = split_step_label(label)
788
- self.step = WorkerStep(i, action, workdir)
819
+ # Create the step
820
+ action, workdir = split_step_label(label)
821
+ self.step = WorkerStep(i, action, workdir)
789
822
 
790
- # Create initial StepHash
791
- return self.compute_inp_step_hash(inp_hashes, env_vars, check_hash)[0]
823
+ # Create initial StepHash
824
+ return self.compute_inp_step_hash(inp_hashes, env_vars, check_hash)[0]
792
825
 
793
826
  def compute_inp_step_hash(
794
827
  self,
@@ -819,41 +852,44 @@ class WorkerHandler:
819
852
  so they can be updated in StepUp's workflow database if desired.
820
853
  Unchanged ones are not included.
821
854
  """
822
- # Check the input hashes
823
- messages = []
824
- new_inp_hashes = []
825
- all_inp_hashes = []
826
- for path, old_file_hash in old_inp_hashes:
827
- new_file_hash = old_file_hash.regen(path)
828
- all_inp_hashes.append((path, new_file_hash))
829
- if check_hash and new_file_hash != old_file_hash:
830
- if new_file_hash.is_unknown:
831
- messages.append(f"Input vanished unexpectedly: {path} ")
832
- else:
833
- messages.append(
834
- f"Input changed unexpectedly: {path} "
835
- + fmt_file_hash_diff(old_file_hash, new_file_hash)
836
- )
837
- new_inp_hashes.append((path, new_file_hash))
838
-
839
- # If there are unexpected issues with inputs, bail out.
840
- if len(messages) > 0:
841
- self.step.inp_messages = messages
842
- self.step.success = False
843
- return None, new_inp_hashes
855
+ with self.timer.section("compute_inp_step_hash"):
856
+ # Check the input hashes
857
+ messages = []
858
+ new_inp_hashes = []
859
+ all_inp_hashes = []
860
+ for path, old_file_hash in old_inp_hashes:
861
+ new_file_hash = old_file_hash.regen(path)
862
+ all_inp_hashes.append((path, new_file_hash))
863
+ if check_hash and new_file_hash != old_file_hash:
864
+ if new_file_hash.is_unknown:
865
+ messages.append(f"Input vanished unexpectedly: {path} ")
866
+ else:
867
+ messages.append(
868
+ f"Input changed unexpectedly: {path} "
869
+ + fmt_file_hash_diff(old_file_hash, new_file_hash)
870
+ )
871
+ new_inp_hashes.append((path, new_file_hash))
872
+
873
+ # If there are unexpected issues with inputs, bail out.
874
+ if len(messages) > 0:
875
+ self.step.inp_messages = messages
876
+ self.step.success = False
877
+ return None, new_inp_hashes
844
878
 
845
- # Add the environment variables to the hash
846
- env_var_values = [(env_var, os.environ.get(env_var)) for env_var in env_vars]
879
+ # Add the environment variables to the hash
880
+ env_var_values = [(env_var, os.environ.get(env_var)) for env_var in env_vars]
847
881
 
848
- # Create the StepHash
849
- label = self.step.action
850
- if self.step.workdir != "./":
851
- label += f" # wd={self.step.workdir}"
852
- result = StepHash.from_inp(f"{label}", self.explain_rerun, all_inp_hashes, env_var_values)
882
+ # Create the StepHash
883
+ label = self.step.action
884
+ if self.step.workdir != "./":
885
+ label += f" # wd={self.step.workdir}"
886
+ result = StepHash.from_inp(
887
+ f"{label}", self.explain_rerun, all_inp_hashes, env_var_values
888
+ )
853
889
 
854
- # Copy the inp_digest, because it can be useful for some actions.
855
- self.step.inp_digest = result.inp_digest
856
- return result, []
890
+ # Copy the inp_digest, because it can be useful for some actions.
891
+ self.step.inp_digest = result.inp_digest
892
+ return result, []
857
893
 
858
894
  @allow_rpc
859
895
  def compute_out_step_hash(
@@ -881,22 +917,23 @@ class WorkerHandler:
881
917
  so they can be updated in StepUp's workflow database if desired.
882
918
  Unchanged ones are not included.
883
919
  """
884
- # Check the output hashes
885
- new_out_hashes = []
886
- all_out_hashes = []
887
- for path, old_file_hash in sorted(old_out_hashes):
888
- new_file_hash = old_file_hash.regen(path)
889
- all_out_hashes.append((path, new_file_hash))
890
- if new_file_hash != old_file_hash:
891
- new_out_hashes.append((path, new_file_hash))
892
- if new_file_hash.is_unknown:
893
- self.step.out_missing.append(path)
894
- self.step.success = False
920
+ with self.timer.section("compute_out_step_hash"):
921
+ # Check the output hashes
922
+ new_out_hashes = []
923
+ all_out_hashes = []
924
+ for path, old_file_hash in sorted(old_out_hashes):
925
+ new_file_hash = old_file_hash.regen(path)
926
+ all_out_hashes.append((path, new_file_hash))
927
+ if new_file_hash != old_file_hash:
928
+ new_out_hashes.append((path, new_file_hash))
929
+ if new_file_hash.is_unknown:
930
+ self.step.out_missing.append(path)
931
+ self.step.success = False
895
932
 
896
- # Update the step hash
897
- if step_hash is not None:
898
- step_hash = step_hash.evolve_out(all_out_hashes)
899
- return step_hash, new_out_hashes
933
+ # Update the step hash
934
+ if step_hash is not None:
935
+ step_hash = step_hash.evolve_out(all_out_hashes)
936
+ return step_hash, new_out_hashes
900
937
 
901
938
  @allow_rpc
902
939
  def compute_full_step_hash(
@@ -956,57 +993,58 @@ class WorkerHandler:
956
993
 
957
994
  @allow_rpc
958
995
  async def run(self):
959
- await self.reporter("START", self.step.description)
960
- await self.reporter.start_step(self.step.description, self.step.i)
961
-
962
- # For internal use in actions:
963
- os.environ["STEPUP_STEP_I"] = str(self.step.i)
964
- # Client code may use the following:
965
- os.environ["STEPUP_STEP_INP_DIGEST"] = self.step.inp_digest.hex()
966
- os.environ["ROOT"] = str(Path.cwd().relpath(self.step.workdir))
967
- os.environ["HERE"] = str(self.step.workdir.relpath())
968
- # Note: the variables defined here should be listed in stepup.core.api.getenv
969
-
970
- # Create IO redirection for stdout and stderr
971
- step_err = io.StringIO()
972
- step_out = io.StringIO()
973
- with (
974
- contextlib.chdir(self.step.workdir),
975
- contextlib.redirect_stderr(step_err),
976
- contextlib.redirect_stdout(step_out),
977
- ):
996
+ with self.timer.section("run"):
997
+ await self.reporter("START", self.step.description)
998
+ await self.reporter.start_step(self.step.description, self.step.i)
999
+
1000
+ # For internal use in actions:
1001
+ os.environ["STEPUP_STEP_I"] = str(self.step.i)
1002
+ # Client code may use the following:
1003
+ os.environ["STEPUP_STEP_INP_DIGEST"] = self.step.inp_digest.hex()
1004
+ os.environ["ROOT"] = str(Path.cwd().relpath(self.step.workdir))
1005
+ os.environ["HERE"] = str(self.step.workdir.relpath())
1006
+ # Note: the variables defined here should be listed in stepup.core.api.getenv
1007
+
1008
+ # Create IO redirection for stdout and stderr
1009
+ step_err = io.StringIO()
1010
+ step_out = io.StringIO()
1011
+ with (
1012
+ contextlib.chdir(self.step.workdir),
1013
+ contextlib.redirect_stderr(step_err),
1014
+ contextlib.redirect_stdout(step_out),
1015
+ ):
1016
+ if self.show_perf:
1017
+ ru_initial = resource.getrusage(resource.RUSAGE_CHILDREN)
1018
+ pt_initial = perf_counter()
1019
+ self.step.thread = WorkThread(self.step.action)
1020
+ self.step.thread.start()
1021
+ await self.step.thread.done.wait()
1022
+ self.step.thread.join()
1023
+ self.step.returncode = self.step.thread.returncode
1024
+ self.step.thread = None
1025
+ self.step.stdout = step_out.getvalue()
1026
+ self.step.stderr = step_err.getvalue()
1027
+
1028
+ # Clean up environment variables (to avoid potential confusion)
1029
+ del os.environ["STEPUP_STEP_I"]
1030
+ del os.environ["STEPUP_STEP_INP_DIGEST"]
1031
+ del os.environ["ROOT"]
1032
+ del os.environ["HERE"]
1033
+
1034
+ # Process results of the step.
978
1035
  if self.show_perf:
979
- ru_initial = resource.getrusage(resource.RUSAGE_CHILDREN)
980
- pt_initial = perf_counter()
981
- self.step.thread = WorkThread(self.step.action)
982
- self.step.thread.start()
983
- await self.step.thread.done.wait()
984
- self.step.thread.join()
985
- self.step.returncode = self.step.thread.returncode
986
- self.step.thread = None
987
- self.step.stdout = step_out.getvalue()
988
- self.step.stderr = step_err.getvalue()
989
-
990
- # Clean up environment variables (to avoid potential confusion)
991
- del os.environ["STEPUP_STEP_I"]
992
- del os.environ["STEPUP_STEP_INP_DIGEST"]
993
- del os.environ["ROOT"]
994
- del os.environ["HERE"]
995
-
996
- # Process results of the step.
997
- if self.show_perf:
998
- ru_final = resource.getrusage(resource.RUSAGE_CHILDREN)
999
- utime = ru_final.ru_utime - ru_initial.ru_utime
1000
- stime = ru_final.ru_stime - ru_initial.ru_stime
1001
- wtime = perf_counter() - pt_initial
1002
- ru_lines = [
1003
- f"User CPU time [s]: {utime:9.4f}",
1004
- f"System CPU time [s]: {stime:9.4f}",
1005
- f"Total CPU time [s]: {utime + stime:9.4f}",
1006
- f"Wall time [s]: {wtime:9.4f}",
1007
- ]
1008
- self.step.perf_info = "\n".join(ru_lines)
1009
- self.step.success = self.step.returncode == 0
1036
+ ru_final = resource.getrusage(resource.RUSAGE_CHILDREN)
1037
+ utime = ru_final.ru_utime - ru_initial.ru_utime
1038
+ stime = ru_final.ru_stime - ru_initial.ru_stime
1039
+ wtime = perf_counter() - pt_initial
1040
+ ru_lines = [
1041
+ f"User CPU time [s]: {utime:9.4f}",
1042
+ f"System CPU time [s]: {stime:9.4f}",
1043
+ f"Total CPU time [s]: {utime + stime:9.4f}",
1044
+ f"Wall time [s]: {wtime:9.4f}",
1045
+ ]
1046
+ self.step.perf_info = "\n".join(ru_lines)
1047
+ self.step.success = self.step.returncode == 0
1010
1048
 
1011
1049
  @allow_rpc
1012
1050
  def get_success(self) -> bool:
@@ -1021,51 +1059,52 @@ class WorkerHandler:
1021
1059
 
1022
1060
  @allow_rpc
1023
1061
  async def report(self):
1024
- pages = []
1025
- if not self.step.success:
1026
- # Format command such that it can be copied and pasted into a shell.
1027
- command = "stepup act "
1028
- if any(word.startswith("-") for word in shlex.split(self.step.action)):
1029
- command += "-- "
1030
- command += self.step.action
1031
- if self.step.workdir != "./":
1032
- command = f"(cd {self.step.workdir} && {command})"
1033
- lines = [f"Command {command}"]
1034
- # Other info on the execution of the step
1035
- if self.step.returncode is not None:
1036
- lines.append(f"Return code {self.step.returncode}")
1037
- pages.append(("Step info", "\n".join(lines)))
1038
- if len(self.step.perf_info) > 0:
1039
- pages.append(("Performance details", self.step.perf_info))
1040
- if self.step.rescheduled_info != "":
1041
- pages.append(
1042
- (
1043
- "Rescheduling due to unavailable amended inputs",
1044
- self.step.rescheduled_info,
1062
+ with self.timer.section("report"):
1063
+ pages = []
1064
+ if not self.step.success:
1065
+ # Format command such that it can be copied and pasted into a shell.
1066
+ command = "stepup act "
1067
+ if any(word.startswith("-") for word in shlex.split(self.step.action)):
1068
+ command += "-- "
1069
+ command += self.step.action
1070
+ if self.step.workdir != "./":
1071
+ command = f"(cd {self.step.workdir} && {command})"
1072
+ lines = [f"Command {command}"]
1073
+ # Other info on the execution of the step
1074
+ if self.step.returncode is not None:
1075
+ lines.append(f"Return code {self.step.returncode}")
1076
+ pages.append(("Step info", "\n".join(lines)))
1077
+ if len(self.step.perf_info) > 0:
1078
+ pages.append(("Performance details", self.step.perf_info))
1079
+ if self.step.rescheduled_info != "":
1080
+ pages.append(
1081
+ (
1082
+ "Rescheduling due to unavailable amended inputs",
1083
+ self.step.rescheduled_info,
1084
+ )
1045
1085
  )
1046
- )
1047
- else:
1048
- if len(self.step.inp_messages) > 0:
1049
- self.step.inp_messages.sort()
1050
- pages.append(("Invalid inputs", "\n".join(self.step.inp_messages)))
1051
- if len(self.step.out_missing) > 0:
1052
- self.step.out_missing.sort()
1053
- pages.append(("Expected outputs not created", "\n".join(self.step.out_missing)))
1054
- stdout = self.step.stdout.rstrip()
1055
- if len(stdout) > 0:
1056
- pages.append(("Standard output", stdout))
1057
- stderr = self.step.stderr.rstrip()
1058
- if len(stderr) > 0:
1059
- pages.append(("Standard error", stderr))
1060
- if self.step.rescheduled_info != "":
1061
- action = "RESCHEDULE"
1062
- elif self.step.success:
1063
- action = "SUCCESS"
1064
- else:
1065
- action = "FAIL"
1066
- await self.reporter.stop_step(self.step.i)
1067
- await self.reporter(action, self.step.description, pages)
1068
- self.step = None
1086
+ else:
1087
+ if len(self.step.inp_messages) > 0:
1088
+ self.step.inp_messages.sort()
1089
+ pages.append(("Invalid inputs", "\n".join(self.step.inp_messages)))
1090
+ if len(self.step.out_missing) > 0:
1091
+ self.step.out_missing.sort()
1092
+ pages.append(("Expected outputs not created", "\n".join(self.step.out_missing)))
1093
+ stdout = self.step.stdout.rstrip()
1094
+ if len(stdout) > 0:
1095
+ pages.append(("Standard output", stdout))
1096
+ stderr = self.step.stderr.rstrip()
1097
+ if len(stderr) > 0:
1098
+ pages.append(("Standard error", stderr))
1099
+ if self.step.rescheduled_info != "":
1100
+ action = "RESCHEDULE"
1101
+ elif self.step.success:
1102
+ action = "SUCCESS"
1103
+ else:
1104
+ action = "FAIL"
1105
+ await self.reporter.stop_step(self.step.i)
1106
+ await self.reporter(action, self.step.description, pages)
1107
+ self.step = None
1069
1108
 
1070
1109
  @allow_rpc
1071
1110
  async def skip(self, step_hash: StepHash):
@@ -1155,6 +1194,7 @@ async def async_main():
1155
1194
  # Create the worker handler for the RPC server.
1156
1195
  handler = WorkerHandler(reporter, args.show_perf, args.explain_rerun)
1157
1196
  await serve_socket_rpc(handler, args.worker_socket, handler.stop_event)
1197
+ handler.timer.report()
1158
1198
 
1159
1199
 
1160
1200
  def main():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stepup
3
- Version: 3.2.1
3
+ Version: 3.2.2
4
4
  Summary: StepUp Core provides the basic framework for the StepUp build tool
5
5
  Author-email: Toon Verstraelen <toon.verstraelen@ugent.be>
6
6
  License-Expression: GPL-3.0-or-later
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes