From a13a8ceb3483d46f45266838047ff8fdaf8eefd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Minier?= Date: Wed, 25 Mar 2026 12:58:49 +0100 Subject: [PATCH 01/11] gen_partition: refactor line reading logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplify logic for readability. Signed-off-by: Loïc Minier --- gen_partition.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/gen_partition.py b/gen_partition.py index f665b99..7d2cd60 100755 --- a/gen_partition.py +++ b/gen_partition.py @@ -258,22 +258,21 @@ def generate_partition_xml(disk_params, partitions, output_xml): print(str(argerr)) usage() f = open(input_file) - line = f.readline() - while line: - if not re.search(r"^\s*#", line) and not re.search(r"^\s*$", line): - line = line.strip() - if re.search("^--disk", line): - if disk_entry is None: - disk_entry = line - else: - print("%s %s" % (sys.argv[1], disk_entry_err_msg)) - print("%s\n%s" % (disk_entry, line)) - sys.exit(1) - elif re.search("^--partition", line): - partition_entries.append(line) + while (line := f.readline()): + if re.search(r"^\s*#", line) or re.search(r"^\s*$", line): + continue + line = line.strip() + if re.search("^--disk", line): + if disk_entry is None: + disk_entry = line else: - print("Ignoring %s" % (line)) - line = f.readline() + print("%s %s" % (sys.argv[1], disk_entry_err_msg)) + print("%s\n%s" % (disk_entry, line)) + sys.exit(1) + elif re.search("^--partition", line): + partition_entries.append(line) + else: + print("Ignoring %s" % (line)) f.close() except Exception as e: print("Error: ", e) From e350a7c1990bab96b02bd6a6801088c48cfa3aa2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Minier?= Date: Tue, 24 Mar 2026 09:49:27 +0100 Subject: [PATCH 02/11] gen_partition: add %include directive support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a pre-processing step that expands %include directives before parsing partitions.conf files. This allows splitting partition definitions into reusable fragments (e.g. shared NHLOS or HLOS partition lists) that can be included from multiple partitions.conf files. Signed-off-by: Loïc Minier --- gen_partition.py | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/gen_partition.py b/gen_partition.py index 7d2cd60..754186d 100755 --- a/gen_partition.py +++ b/gen_partition.py @@ -29,6 +29,7 @@ import getopt import re +import os import sys import xml.etree.ElementTree as ET from collections import OrderedDict @@ -77,6 +78,36 @@ def usage(): output_xml = None +def expand_includes(filepath, include_stack=None): + """Read a file and expand %include directives recursively. + + Returns a list of stripped lines. + Paths in %include are resolved relative to the including file's directory. + Raises ValueError on circular includes or missing files. + """ + if include_stack is None: + include_stack = [] + filepath = os.path.realpath(filepath) + if not os.path.exists(filepath): + raise ValueError("File not found: %s" % filepath) + if filepath in include_stack: + raise ValueError("Circular include detected: %s\nInclude stack: %s" % ( + filepath, " -> ".join(include_stack))) + # make a copy, instead of mutating as we're recursing + include_stack = include_stack + [filepath] + base_dir = os.path.dirname(filepath) + lines = [] + with open(filepath) as f: + for raw_line in f: + stripped = raw_line.strip() + if stripped.startswith('%include '): + inc_path = stripped[len('%include '):] + inc_full = os.path.join(base_dir, inc_path) + lines.extend(expand_includes(inc_full, include_stack)) + else: + lines.append(stripped) + return lines + def disk_options(argv): disk_params = disk_params_defaults.copy() for opt, arg in argv: @@ -257,11 +288,9 @@ def generate_partition_xml(disk_params, partitions, output_xml): except Exception as argerr: print(str(argerr)) usage() - f = open(input_file) - while (line := f.readline()): + for line in expand_includes(input_file): if re.search(r"^\s*#", line) or re.search(r"^\s*$", line): continue - line = line.strip() if re.search("^--disk", line): if disk_entry is None: disk_entry = line @@ -273,7 +302,7 @@ def generate_partition_xml(disk_params, partitions, output_xml): partition_entries.append(line) else: print("Ignoring %s" % (line)) - f.close() + except Exception as e: print("Error: ", e) sys.exit(1) From abe746b883661f6a1ad0b0c8080b3a02e8adc63e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Minier?= Date: Tue, 24 Mar 2026 11:40:14 +0100 Subject: [PATCH 03/11] gen_partition: add multi-disk support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow partitions.conf files to contain multiple --disk sections. Each --disk line starts a new section; partitions following it belong to that disk. Automatically generate partitionN.xml files if more than one disk is listed. This enables a single partitions.conf to describe a complete platform spanning multiple storage types (e.g. SPINOR + NVMe for Glymur CRD). Signed-off-by: Loïc Minier --- gen_partition.py | 51 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/gen_partition.py b/gen_partition.py index 754186d..147a78a 100755 --- a/gen_partition.py +++ b/gen_partition.py @@ -69,9 +69,6 @@ def usage(): } ################################################################## -# store entries read from input file -disk_entry = None -partition_entries = [] # store partition image map passed from command line partition_image_map = {} input_file = None @@ -262,7 +259,6 @@ def generate_partition_xml(disk_params, partitions, output_xml): ############################################################################### # main -disk_entry_err_msg = "contains more than one --disk entries" if len(sys.argv) < 3: usage() @@ -288,27 +284,50 @@ def generate_partition_xml(disk_params, partitions, output_xml): except Exception as argerr: print(str(argerr)) usage() + + # Parse expanded lines into disk sections + disk_sections = [] # list of (disk_line, [partition_lines]) + current_disk = None + current_partitions = [] + for line in expand_includes(input_file): if re.search(r"^\s*#", line) or re.search(r"^\s*$", line): continue if re.search("^--disk", line): - if disk_entry is None: - disk_entry = line - else: - print("%s %s" % (sys.argv[1], disk_entry_err_msg)) - print("%s\n%s" % (disk_entry, line)) - sys.exit(1) + if current_disk is not None: + disk_sections.append((current_disk, current_partitions)) + current_partitions = [] + current_disk = line elif re.search("^--partition", line): - partition_entries.append(line) + current_partitions.append(line) else: print("Ignoring %s" % (line)) - + if current_disk is not None: + disk_sections.append((current_disk, current_partitions)) + + if not disk_sections: + print("Error: no --disk entry found in %s" % input_file) + sys.exit(1) + + if output_xml: + if len(disk_sections) == 1: + disk_params = parse_disk_entry(disk_sections[0][0]) + partitions = parse_partition_entries(disk_sections[0][1]) + generate_partition_xml(disk_params, partitions, output_xml) + else: + # Multi-disk: derive output filenames by inserting an index before + # the extension, e.g. partitions.xml -> partitions0.xml, partitions1.xml + base, ext = os.path.splitext(output_xml) + for idx, (disk_line, part_lines) in enumerate(disk_sections): + disk_params = parse_disk_entry(disk_line) + partitions = parse_partition_entries(part_lines) + out_path = "%s%d%s" % (base, idx, ext) + generate_partition_xml(disk_params, partitions, out_path) + else: + print("Error: -o is required") + sys.exit(1) except Exception as e: print("Error: ", e) sys.exit(1) -disk_params = parse_disk_entry(disk_entry) -partitions = parse_partition_entries(partition_entries) -generate_partition_xml(disk_params, partitions, output_xml) - sys.exit(0) From ddd96d7e8eff58f5d12cd9e3769a56aaa222596a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Minier?= Date: Tue, 24 Mar 2026 20:35:35 +0100 Subject: [PATCH 04/11] tests: integration tests for %include and multi-disk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add check-include-multidisk covering various %include and multi-disk scenarios. Call as part of integration tests (integration make target). Signed-off-by: Loïc Minier --- Makefile | 2 + tests/integration/check-include-multidisk | 163 ++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100755 tests/integration/check-include-multidisk diff --git a/Makefile b/Makefile index e2b8f68..14d78dd 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,8 @@ lint: integration: all # make sure generated output has created expected files tests/integration/check-missing-files platforms/*/*/*.xml + # test %include and multi-disk features + tests/integration/check-include-multidisk check: lint integration diff --git a/tests/integration/check-include-multidisk b/tests/integration/check-include-multidisk new file mode 100755 index 0000000..a375e42 --- /dev/null +++ b/tests/integration/check-include-multidisk @@ -0,0 +1,163 @@ +#!/bin/sh +# Copyright (c) 2026 Qualcomm Innovation Center, Inc. All rights reserved. +# SPDX-License-Identifier: BSD-3-Clause-Clear + +# Test %include directive and multi-disk support in gen_partition.py + +set -eu + +TOPDIR="$(cd "$(dirname "$0")/../.." && pwd)" +GEN="${TOPDIR}/gen_partition.py" +TMPDIR="$(mktemp -d)" +trap 'rm -rf "${TMPDIR}"' EXIT + +errors=0 +fail() { echo "FAIL: $1" >&2; errors=$((errors + 1)); } +pass() { echo "PASS: $1"; } + +########################################################################## +# Test 1: basic %include resolves correctly +########################################################################## +cat > "${TMPDIR}/disk.conf.inc" <<'EOF' +--disk --type=emmc --size=1073741824 --sector-size-in-bytes=512 +EOF +cat > "${TMPDIR}/parts.conf.inc" <<'EOF' +--partition --name=boot --size=4096KB --type-guid=20117F86-E985-4357-B9EE-374BC1D8487D +EOF +cat > "${TMPDIR}/main.conf" <<'EOF' +%include disk.conf.inc +%include parts.conf.inc +EOF + +"${GEN}" -i "${TMPDIR}/main.conf" -o "${TMPDIR}/out1.xml" 2>&1 +if grep -q 'label="boot"' "${TMPDIR}/out1.xml"; then + pass "basic %include" +else + fail "basic %include: partition not found in output" +fi + +########################################################################## +# Test 2: nested %include +########################################################################## +mkdir -p "${TMPDIR}/sub" +cat > "${TMPDIR}/sub/nested.conf.inc" <<'EOF' +--partition --name=rootfs --size=8192KB --type-guid=B921B045-1DF0-41C3-AF44-4C6F280D3FAE +EOF +cat > "${TMPDIR}/outer.conf.inc" <<'EOF' +%include sub/nested.conf.inc +EOF +cat > "${TMPDIR}/nest.conf" <<'EOF' +%include disk.conf.inc +%include outer.conf.inc +EOF + +"${GEN}" -i "${TMPDIR}/nest.conf" -o "${TMPDIR}/out2.xml" 2>&1 +if grep -q 'label="rootfs"' "${TMPDIR}/out2.xml"; then + pass "nested %include" +else + fail "nested %include: partition not found in output" +fi + +########################################################################## +# Test 3: circular %include detection +########################################################################## +cat > "${TMPDIR}/a.conf" < "${TMPDIR}/b.conf" <&1; then + fail "circular %include: should have failed" +else + pass "circular %include detected" +fi + +########################################################################## +# Test 4: multi-disk generates indexed output files +########################################################################## +cat > "${TMPDIR}/multi.conf" <<'EOF' +--disk --type=spinor --size=67108864 --sector-size-in-bytes=4096 +--partition --name=xbl --size=1024KB --type-guid=DEA0BA2C-CBDD-4805-B4F9-F428251C3E98 +--disk --type=nvme --size=68719476736 --sector-size-in-bytes=512 +--partition --name=efi --size=524288KB --type-guid=C12A7328-F81F-11D2-BA4B-00A0C93EC93B +EOF + +"${GEN}" -i "${TMPDIR}/multi.conf" -o "${TMPDIR}/multi-out.xml" 2>&1 +if [ -f "${TMPDIR}/multi-out0.xml" ] && \ + [ -f "${TMPDIR}/multi-out1.xml" ]; then + pass "multi-disk indexed output files" +else + fail "multi-disk: expected multi-out0.xml and multi-out1.xml" +fi + +if grep -q 'label="xbl"' "${TMPDIR}/multi-out0.xml" && \ + grep -q 'label="efi"' "${TMPDIR}/multi-out1.xml"; then + pass "multi-disk partition placement" +else + fail "multi-disk: partitions in wrong XML" +fi + +########################################################################## +# Test 5: single-disk with -o writes directly (no index) +########################################################################## +cat > "${TMPDIR}/single.conf" <<'EOF' +--disk --type=ufs --size=76841669632 --sector-size-in-bytes=4096 +--partition --name=boot --size=4096KB --type-guid=20117F86-E985-4357-B9EE-374BC1D8487D +EOF + +"${GEN}" -i "${TMPDIR}/single.conf" -o "${TMPDIR}/single-out.xml" 2>&1 +if [ -f "${TMPDIR}/single-out.xml" ]; then + pass "single-disk -o mode" +else + fail "single-disk -o: partitions.xml not written" +fi + +########################################################################## +# Test 6: missing %include file gives an error +########################################################################## +cat > "${TMPDIR}/missing.conf" <<'EOF' +%include nonexistent.conf.inc +EOF + +if "${GEN}" -i "${TMPDIR}/missing.conf" -o "${TMPDIR}/missing.xml" 2>&1; then + fail "missing %include: should have failed" +else + pass "missing %include detected" +fi + +########################################################################## +# Test 7: %include with multi-disk +########################################################################## +cat > "${TMPDIR}/spinor.conf.inc" <<'EOF' +--disk --type=spinor --size=67108864 --sector-size-in-bytes=4096 +--partition --name=fw --size=1024KB --type-guid=DEA0BA2C-CBDD-4805-B4F9-F428251C3E98 +EOF +cat > "${TMPDIR}/nvme.conf.inc" <<'EOF' +--disk --type=nvme --size=68719476736 --sector-size-in-bytes=512 +--partition --name=efi --size=524288KB --type-guid=C12A7328-F81F-11D2-BA4B-00A0C93EC93B +EOF +cat > "${TMPDIR}/combo.conf" <<'EOF' +%include spinor.conf.inc +%include nvme.conf.inc +EOF + +"${GEN}" -i "${TMPDIR}/combo.conf" -o "${TMPDIR}/combo.xml" 2>&1 +if [ -f "${TMPDIR}/combo0.xml" ] && \ + [ -f "${TMPDIR}/combo1.xml" ]; then + pass "%include with multi-disk" +else + fail "%include with multi-disk: expected combo0.xml and combo1.xml" +fi + +########################################################################## +# Summary +########################################################################## +if [ "${errors}" -ne 0 ]; then + echo "${errors} test(s) failed" >&2 + exit 1 +fi +echo "All tests passed" +exit 0 From e50bd1f775d148ceb6e259d4a3f68de6eb11d976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Minier?= Date: Wed, 25 Mar 2026 15:19:59 +0100 Subject: [PATCH 05/11] gen_contents: prefix file paths for multi-disk MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In multi-disk layouts, partitions.xml files are in per-storage subdirs (e.g. spinor-nvme/spinor/partitions.xml). Set file_path from the relative path between the output directory and the partitions.xml directory. Single-disk layouts where both files are in the same directory are unaffected (prefix is empty). Signed-off-by: Loïc Minier --- gen_contents.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/gen_contents.py b/gen_contents.py index 0261601..789f303 100755 --- a/gen_contents.py +++ b/gen_contents.py @@ -11,7 +11,7 @@ def usage(): print( ( - "\n\tUsage: %s -t