fix: add process tracking and cancellation support to command execution

This commit is contained in:
Даниил Грабарь
2026-05-22 13:23:38 +10:00
parent 14337aa4e7
commit 4202421d5b
6 changed files with 224 additions and 73 deletions
+38 -14
View File
@@ -68,6 +68,7 @@ def _build_image(
on_progress: Optional[Callable[[float], None]] = None,
parent_tag: Optional[str] = None,
build_type: str = "release",
register_proc: Optional[Callable] = None,
) -> bool:
write(f"[cyan][*][/cyan] Building [bold]{name}[/bold]...")
# DOCKER_BUILDKIT=0 gives us "Step X/Y" lines in the output which we parse for progress.
@@ -84,6 +85,8 @@ def _build_image(
proc = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, env=env,
)
if register_proc:
register_proc(proc)
for line in proc.stdout:
s = line.rstrip()
if s:
@@ -94,6 +97,8 @@ def _build_image(
step, total = int(m.group(1)), int(m.group(2))
on_progress(step / total * 100)
proc.wait()
if proc.returncode in (-9, -15):
return False
if proc.returncode == 0:
write(f"[green][ok][/green] {name}")
return True
@@ -102,12 +107,13 @@ def _build_image(
# Pull a Docker image from Hub and track progress by counting downloaded layers.
# Скачиваем Docker-образ с Hub и отслеживаем прогресс по количеству скачанных слоёв.
# Скачиваем Docker-образ с Hub и отслеживаем прогресс по количеством скачанных слоёв.
def _pull_image(
name: str,
tag: str,
write: Write,
on_progress: Optional[Callable[[float], None]] = None,
register_proc: Optional[Callable] = None,
) -> bool:
write(f"[cyan][*][/cyan] Pulling [bold]{name}[/bold] ({tag})...")
if on_progress:
@@ -117,6 +123,8 @@ def _pull_image(
["docker", "pull", tag],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True,
)
if register_proc:
register_proc(proc)
layers_total = 0
layers_done = 0
for line in proc.stdout:
@@ -132,6 +140,8 @@ def _pull_image(
if on_progress and layers_total > 0:
on_progress(5 + layers_done / layers_total * 90)
proc.wait()
if proc.returncode in (-9, -15):
return False
if proc.returncode == 0:
write(f"[green][ok][/green] {name}")
if on_progress:
@@ -156,6 +166,8 @@ def _task_execute(screen: LogScreen, cfg: _Config) -> None:
f"ROS {cfg.ros_version}{cfg.build_type}[/bold]\n"
)
for i, name in enumerate(chain):
if screen.is_stopped():
return
lo = i / n * 100
hi = (i + 1) / n * 100
screen.set_progress(lo, f"Image {i + 1}/{n}: building {name}...")
@@ -164,7 +176,8 @@ def _task_execute(screen: LogScreen, cfg: _Config) -> None:
dockerfile = _DOCKER_DIR / cfg.ros_version / name / "Dockerfile"
if not dockerfile.exists():
screen.write(f"[red]Dockerfile not found:[/red] {dockerfile}")
screen.finish(False)
if not screen.is_stopped():
screen.finish(False)
return
ctx = _PROJECT_DIR if name in _NEEDS_PROJECT_CTX else dockerfile.parent
parent_name = _IMAGE_PARENT.get(name)
@@ -172,23 +185,28 @@ def _task_execute(screen: LogScreen, cfg: _Config) -> None:
f"{cfg.image_prefix}:{parent_name}-{cfg.ros_version}"
if parent_name else None
)
if not _build_image(
ok = _build_image(
name, tag, dockerfile, ctx, screen.write,
on_progress=lambda p, lo=lo, hi=hi: screen.set_progress(
lo + p * (hi - lo) / 100, f"Image {i + 1}/{n}: building {name}..."
),
parent_tag=parent_tag,
build_type=cfg.build_type,
):
register_proc=screen.set_proc,
)
if screen.is_stopped():
return
if not ok:
screen.finish(False)
return
screen.set_progress(hi)
screen.set_progress(100, "All images built")
screen.write(
f"\n[green]Done.[/green] "
f"Images tagged [bold]{cfg.image_prefix}:<name>-{cfg.ros_version}[/bold]."
)
if not screen.is_stopped():
screen.set_progress(100, "All images built")
screen.write(
f"\n[green]Done.[/green] "
f"Images tagged [bold]{cfg.image_prefix}:<name>-{cfg.ros_version}[/bold]."
)
else:
short = "webots" if cfg.variant == "webots" else "iiwa"
@@ -199,20 +217,26 @@ def _task_execute(screen: LogScreen, cfg: _Config) -> None:
f"ROS {cfg.ros_version}{cfg.build_type}[/bold]\n"
)
screen.set_progress(0, f"Pulling {full_ref}...")
if not _pull_image(
ok = _pull_image(
short, full_ref, screen.write,
on_progress=lambda p: screen.set_progress(p, f"Pulling {full_ref}..."),
):
register_proc=screen.set_proc,
)
if screen.is_stopped():
return
if not ok:
screen.finish(False)
return
screen.set_progress(100, "Pull complete")
screen.write(f"\n[green]Done.[/green] Image ready: [bold]{full_ref}[/bold].")
screen.finish(True)
if not screen.is_stopped():
screen.finish(True)
except Exception as exc:
screen.write(f"\n[red]Error:[/red] {exc}")
screen.finish(False)
if not screen.is_stopped():
screen.write(f"\n[red]Error:[/red] {exc}")
screen.finish(False)
# Scan the docker/ directory for subdirectories named after ROS versions (e.g. jazzy).