fix(rootfs): persist semantic internal-ci resume target instead of resuming from script head

This commit is contained in:
vxtls 2026-03-07 20:46:03 -05:00
parent bab05bbbe3
commit 74da573c33
2 changed files with 107 additions and 16 deletions

113
rootfs.py
View file

@ -42,8 +42,24 @@ def parse_internal_ci_break_after(value):
) )
return scope, step_name return scope, step_name
def _parse_build_step_name(line):
"""
Extract the package name from a manifest build directive.
"""
stripped = line.strip()
if not stripped.startswith("build: "):
return None
payload = stripped[len("build: "):].split("#", 1)[0].strip()
if not payload:
return None
return payload.split()[0]
_INIT_MOUNT_MARKER = "# LB_STAGE0_EARLY_MOUNTS" _INIT_MOUNT_MARKER = "# LB_STAGE0_EARLY_MOUNTS"
_INIT_REGEN_MARKER = "# LB_STAGE0_REGENERATE_SCRIPTS" _INIT_REGEN_MARKER = "# LB_STAGE0_REGENERATE_SCRIPTS"
_RESUME_NEXT_PATH = "/steps/.lb-resume-next"
_RESUME_NEXT_SCOPE_VAR = "LB_RESUME_NEXT_SCOPE"
_RESUME_NEXT_PACKAGE_VAR = "LB_RESUME_NEXT_PACKAGE"
_INIT_LEGACY_NETWORK_BLOCK = ( _INIT_LEGACY_NETWORK_BLOCK = (
'if [ "${CHROOT}" = False ] && command -v dhcpcd >/dev/null 2>&1; then\n' 'if [ "${CHROOT}" = False ] && command -v dhcpcd >/dev/null 2>&1; then\n'
'dhcpcd --waitip=4 || true\n' 'dhcpcd --waitip=4 || true\n'
@ -68,6 +84,8 @@ _INIT_REGEN_BLOCK = (
+ 'resume_entry=""\n' + 'resume_entry=""\n'
+ 'resume_root=""\n' + 'resume_root=""\n'
+ 'resume_pkg=""\n' + 'resume_pkg=""\n'
+ 'resume_next_scope=""\n'
+ 'resume_next_pkg=""\n'
+ 'if [ -f "$0" ]; then\n' + 'if [ -f "$0" ]; then\n'
+ 'resume_entry="$(sed -n "s/.*bash \\(\\/steps[^ ]*\\/[0-9][0-9]*\\.sh\\).*/\\1/p" "$0" | head -n1)"\n' + 'resume_entry="$(sed -n "s/.*bash \\(\\/steps[^ ]*\\/[0-9][0-9]*\\.sh\\).*/\\1/p" "$0" | head -n1)"\n'
+ 'fi\n' + 'fi\n'
@ -77,6 +95,22 @@ _INIT_REGEN_BLOCK = (
+ 'resume_pkg="$(sed -n "s/^build \\([^ ]*\\) .*/\\1/p" "${resume_entry}" | head -n1)"\n' + 'resume_pkg="$(sed -n "s/^build \\([^ ]*\\) .*/\\1/p" "${resume_entry}" | head -n1)"\n'
+ 'fi\n' + 'fi\n'
+ 'fi\n' + 'fi\n'
+ f'if [ -f "{_RESUME_NEXT_PATH}" ]; then\n'
+ f'resume_next_scope="$(sed -n "s/^scope=//p" "{_RESUME_NEXT_PATH}" | head -n1)"\n'
+ f'resume_next_pkg="$(sed -n "s/^package=//p" "{_RESUME_NEXT_PATH}" | head -n1)"\n'
+ 'case "${resume_next_scope}" in\n'
+ 'steps)\n'
+ 'resume_root="/steps"\n'
+ ';;\n'
+ 'steps-guix)\n'
+ 'resume_root="/steps-guix"\n'
+ ';;\n'
+ 'esac\n'
+ 'if [ -n "${resume_next_pkg}" ]; then\n'
+ 'resume_pkg="${resume_next_pkg}"\n'
+ 'fi\n'
+ f'rm -f "{_RESUME_NEXT_PATH}"\n'
+ 'fi\n'
+ 'if [ -x /script-generator ] && [ -f /steps/manifest ]; then\n' + 'if [ -x /script-generator ] && [ -f /steps/manifest ]; then\n'
+ '/script-generator /steps/manifest\n' + '/script-generator /steps/manifest\n'
+ 'fi\n' + 'fi\n'
@ -109,17 +143,42 @@ _INIT_REGEN_BLOCK = (
) )
def _find_next_build_after(manifest_lines, break_step, context_label):
"""
Return the next build step after break_step within the same manifest.
"""
found_break = False
for line in manifest_lines:
step_name = _parse_build_step_name(line)
if step_name is None:
continue
if found_break:
return step_name
if step_name == break_step:
found_break = True
if not found_break:
raise ValueError(
"INTERNAL_CI_BREAK_AFTER target not found in "
+ context_label
+ ": "
+ break_step
)
return None
def _insert_internal_ci_break(manifest_lines, break_step, internal_ci, context_label): def _insert_internal_ci_break(manifest_lines, break_step, internal_ci, context_label):
inserted = False inserted = False
break_line = f"jump: break ( INTERNAL_CI == {internal_ci} )\n" break_line = f"jump: break ( INTERNAL_CI == {internal_ci} )\n"
output_lines = [] output_lines = []
for line in manifest_lines: for line in manifest_lines:
output_lines.append(line) output_lines.append(line)
stripped = line.strip() if inserted:
if inserted or not stripped.startswith("build: "):
continue continue
step_name = stripped[len("build: "):].split("#", 1)[0].strip() step_name = _parse_build_step_name(line)
if step_name is None:
continue
if step_name == break_step: if step_name == break_step:
output_lines.append(break_line) output_lines.append(break_line)
inserted = True inserted = True
@ -200,6 +259,9 @@ def _update_stage0_tree(mountpoint,
raise SystemExit(f"Missing config in stage0 image: {old_config_path}") raise SystemExit(f"Missing config in stage0 image: {old_config_path}")
old_env_path = os.path.join(mountpoint, "steps", "env") old_env_path = os.path.join(mountpoint, "steps", "env")
old_env_content = None old_env_content = None
break_output_lines = None
break_manifest_relpath = None
next_step = None
if os.path.isfile(old_env_path): if os.path.isfile(old_env_path):
with open(old_env_path, "rb") as env_file: with open(old_env_path, "rb") as env_file:
old_env_content = env_file.read() old_env_content = env_file.read()
@ -209,6 +271,8 @@ def _update_stage0_tree(mountpoint,
line for line in cfg line for line in cfg
if not line.startswith("BUILD_GUIX_ALSO=") if not line.startswith("BUILD_GUIX_ALSO=")
and not line.startswith("INTERNAL_CI=") and not line.startswith("INTERNAL_CI=")
and not line.startswith(_RESUME_NEXT_SCOPE_VAR + "=")
and not line.startswith(_RESUME_NEXT_PACKAGE_VAR + "=")
and not line.startswith("MIRRORS=") and not line.startswith("MIRRORS=")
and not line.startswith("MIRRORS_LEN=") and not line.startswith("MIRRORS_LEN=")
and not line.startswith("PAYLOAD_REQUIRED=") and not line.startswith("PAYLOAD_REQUIRED=")
@ -233,6 +297,33 @@ def _update_stage0_tree(mountpoint,
env_file.write("\n") env_file.write("\n")
env_file.write("NETWORK_READY=False\n") env_file.write("NETWORK_READY=False\n")
if break_scope and break_step:
if internal_ci in ("", "False", None):
raise SystemExit("INTERNAL_CI must be set when INTERNAL_CI_BREAK_AFTER is used.")
source_manifest_path = os.path.join(
steps_guix_dir if break_scope == "steps-guix" else steps_dir,
"manifest",
)
if not os.path.isfile(source_manifest_path):
raise SystemExit(f"Missing manifest for INTERNAL_CI_BREAK_AFTER: {source_manifest_path}")
with open(source_manifest_path, "r", encoding="utf-8") as manifest_file:
manifest_lines = manifest_file.readlines()
next_step = _find_next_build_after(
manifest_lines,
break_step,
f"{break_scope}/manifest",
)
break_output_lines = _insert_internal_ci_break(
manifest_lines,
break_step,
internal_ci,
f"{break_scope}/manifest",
)
break_manifest_relpath = os.path.join(break_scope, "manifest")
if next_step:
lines.append(f"{_RESUME_NEXT_SCOPE_VAR}={break_scope}\n")
lines.append(f"{_RESUME_NEXT_PACKAGE_VAR}={next_step}\n")
config_path = os.path.join(dest_steps, "bootstrap.cfg") config_path = os.path.join(dest_steps, "bootstrap.cfg")
if build_guix_also: if build_guix_also:
lines.append("BUILD_GUIX_ALSO=True\n") lines.append("BUILD_GUIX_ALSO=True\n")
@ -250,22 +341,12 @@ def _update_stage0_tree(mountpoint,
dest_steps_guix = os.path.join(mountpoint, "steps-guix") dest_steps_guix = os.path.join(mountpoint, "steps-guix")
_copytree_replace(steps_guix_dir, dest_steps_guix) _copytree_replace(steps_guix_dir, dest_steps_guix)
if break_scope and break_step: if break_output_lines is not None and break_manifest_relpath is not None:
if internal_ci in ("", "False", None): manifest_path = os.path.join(mountpoint, break_manifest_relpath)
raise SystemExit("INTERNAL_CI must be set when INTERNAL_CI_BREAK_AFTER is used.")
manifest_path = os.path.join(mountpoint, break_scope, "manifest")
if not os.path.isfile(manifest_path): if not os.path.isfile(manifest_path):
raise SystemExit(f"Missing manifest for INTERNAL_CI_BREAK_AFTER: {manifest_path}") raise SystemExit(f"Missing manifest for INTERNAL_CI_BREAK_AFTER: {manifest_path}")
with open(manifest_path, "r", encoding="utf-8") as manifest_file:
manifest_lines = manifest_file.readlines()
output_lines = _insert_internal_ci_break(
manifest_lines,
break_step,
internal_ci,
f"{break_scope}/manifest",
)
with open(manifest_path, "w", encoding="utf-8") as manifest_file: with open(manifest_path, "w", encoding="utf-8") as manifest_file:
manifest_file.writelines(output_lines) manifest_file.writelines(break_output_lines)
def _stage0_update_cli(argv): def _stage0_update_cli(argv):

View file

@ -4,4 +4,14 @@
# #
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
resume_next_path="/steps/.lb-resume-next"
if [ -n "${LB_RESUME_NEXT_SCOPE:-}" ] && [ -n "${LB_RESUME_NEXT_PACKAGE:-}" ]; then
mkdir -p "$(dirname "${resume_next_path}")"
cat > "${resume_next_path}" <<EOF
scope=${LB_RESUME_NEXT_SCOPE}
package=${LB_RESUME_NEXT_PACKAGE}
EOF
fi
exit 0 exit 0