From dd2fff17a568f6014aa6eb109b40630f18240850 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Tue, 8 Sep 2026 21:53:41 +0200 Subject: [PATCH 1/2] cmds.projects.CmdCreatePkgConfig: Fix Requires lines __cleanup_requires() replaces every run of whitespace with ", " before re-pairing the version constraints, so input that is already comma-separated, e.g. "jw-core >= 1.0, jw-base", comes out with a double comma, "jw-core >= 1.0,, jw-base". And the Requires line is appended without a trailing newline, so a following Requires.private line runs straight into it. Split the input on commas and whitespace, treating the version constraint operators as delimiters that are re-paired with the preceding name and the following version, so that comma- and space-separated input alike comes out as a clean ", "-joined list. Add the missing newline after the Requires line. Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v0.84.2 Signed-off-by: Jan Lindemann --- .../pkg/cmds/projects/CmdCreatePkgConfig.py | 29 +++++++++++------ .../cmds/projects/CmdCreatePkgConfig/Makefile | 8 +++++ .../cmds/projects/CmdCreatePkgConfig/test.py | 31 +++++++++++++++++++ 3 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 test/unit/python/jw/pkg/cmds/projects/CmdCreatePkgConfig/Makefile create mode 100644 test/unit/python/jw/pkg/cmds/projects/CmdCreatePkgConfig/test.py diff --git a/src/python/jw/pkg/cmds/projects/CmdCreatePkgConfig.py b/src/python/jw/pkg/cmds/projects/CmdCreatePkgConfig.py index 7c0d9684..0288dae8 100644 --- a/src/python/jw/pkg/cmds/projects/CmdCreatePkgConfig.py +++ b/src/python/jw/pkg/cmds/projects/CmdCreatePkgConfig.py @@ -23,16 +23,25 @@ class CmdCreatePkgConfig(Cmd): # export def __cleanup_requires(string: str) -> str: import re - regexes = [ - (r'^ +', ''), - (r'([ \t]|$)+', ', '), - (r', $', ''), - (r', $', ''), - (r' *,* *([<>=]+) *,* *', r' \1 '), - ] - for patt, replacement in regexes: - string = re.sub(patt, replacement, string) - return string + rx_op = r'(!=|<=|>=|==|[<>=])' + ret: list[str] = [] + for element in string.split(','): + # -- Separate the version constraints from their operands, + # which turns every package name, operator, and version into + # its own whitespace-separated token + element = re.sub(rx_op, r' \1 ', element) + tokens = element.split() + i = 0 + while i < len(tokens): + if (i + 2 < len(tokens) + and re.fullmatch(rx_op, tokens[i + 1]) is not None): + # -- Merge the name, operator, and version back together + ret.append(' '.join(tokens[i:i + 3])) + i += 3 + continue + ret.append(tokens[i]) + i += 1 + return ', '.join(ret) @override def add_arguments(self, parser: ArgumentParser) -> None: diff --git a/test/unit/python/jw/pkg/cmds/projects/CmdCreatePkgConfig/Makefile b/test/unit/python/jw/pkg/cmds/projects/CmdCreatePkgConfig/Makefile new file mode 100644 index 00000000..47708b2e --- /dev/null +++ b/test/unit/python/jw/pkg/cmds/projects/CmdCreatePkgConfig/Makefile @@ -0,0 +1,8 @@ +TOPDIR = ../../../../../../../.. + +include $(TOPDIR)/make/proj.mk +include $(JWBDIR)/make/py-run.mk + +all: + +test: run diff --git a/test/unit/python/jw/pkg/cmds/projects/CmdCreatePkgConfig/test.py b/test/unit/python/jw/pkg/cmds/projects/CmdCreatePkgConfig/test.py new file mode 100644 index 00000000..ef6c1d92 --- /dev/null +++ b/test/unit/python/jw/pkg/cmds/projects/CmdCreatePkgConfig/test.py @@ -0,0 +1,31 @@ +from jw.pkg.cmds.projects.CmdCreatePkgConfig import CmdCreatePkgConfig + +# __cleanup_requires() is a private staticmethod; reach it via getattr +cleanup = getattr(CmdCreatePkgConfig, '_CmdCreatePkgConfig__cleanup_requires') + +# -- __cleanup_requires -- + +# Comma- and space-separated input alike comes out as a clean ", "-joined list +assert cleanup('jw-core >= 1.0, jw-base') == 'jw-core >= 1.0, jw-base' +assert cleanup('jw-core >= 1.0 jw-base') == 'jw-core >= 1.0, jw-base' +assert cleanup('jw-core >= 1.0, jw-base') == 'jw-core >= 1.0, jw-base' + +# Plain names without version constraints are joined the same way +assert cleanup('a b c') == 'a, b, c' + +# A single element is unchanged +assert cleanup('jw-core >= 1.0') == 'jw-core >= 1.0' + +# The empty input yields the empty output +assert cleanup('') == '' + +# Every constraint operator is re-paired with its name and version +assert ( + cleanup('x <= 2, y != 3, z == 4, w > 5, v < 6, u = 7') == + 'x <= 2, y != 3, z == 4, w > 5, v < 6, u = 7' +) + +# Whitespace around a separator collapses to a single ", " +assert cleanup('jw-a = 1.0 , jw-b = 2.0') == 'jw-a = 1.0, jw-b = 2.0' + +print('All CmdCreatePkgConfig tests passed') -- 2.55.0 From cb07ccdb7ab22f0633942e01c70f17d0fd5efcd8 Mon Sep 17 00:00:00 2001 From: Jan Lindemann Date: Tue, 8 Sep 2026 22:08:20 +0200 Subject: [PATCH 2/2] test/integration/projects/create-pkg-config: Add create-pkg-config's output is not covered by any test, even though the template and the Requires cleanup produce exact text that pkg-config consumes, so regressions in the generated file went unnoticed for a long time. Add an integration test that runs create-pkg-config with a full option set, with space- and comma-mixed Requires input, and with a Requires.private-only input, and compares the output against a reference file. Assisted-by: unsloth/Qwen3.8-27B-GGUF:Q4_K_M with pi.dev v0.84.2 Signed-off-by: Jan Lindemann --- .../projects/create-pkg-config/Makefile | 19 ++++++++++++ .../create-pkg-config/test-expected.txt | 31 +++++++++++++++++++ .../jw-pkg/projects/create-pkg-config/test.sh | 28 +++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 test/integration/jw-pkg/projects/create-pkg-config/Makefile create mode 100644 test/integration/jw-pkg/projects/create-pkg-config/test-expected.txt create mode 100644 test/integration/jw-pkg/projects/create-pkg-config/test.sh diff --git a/test/integration/jw-pkg/projects/create-pkg-config/Makefile b/test/integration/jw-pkg/projects/create-pkg-config/Makefile new file mode 100644 index 00000000..4289f7ea --- /dev/null +++ b/test/integration/jw-pkg/projects/create-pkg-config/Makefile @@ -0,0 +1,19 @@ +TOPDIR = ../../../../.. + +OUTPUT = test-out.txt +REFERENCE = test-expected.txt + +include $(TOPDIR)/make/proj.mk +include $(TOPDIR)/make/test-jw-pkg.mk + +all: + +$(OUTPUT): Makefile test.sh + bash ./test.sh $(TEST_CMD_LINE) > $(OUTPUT).tmp + diff $(REFERENCE) $(OUTPUT).tmp + mv $(OUTPUT).tmp $(OUTPUT) + +test: $(OUTPUT) +clean: test.integration.in-tree.clean +test.integration.in-tree.clean: + rm -f $(OUTPUT) $(OUTPUT).tmp diff --git a/test/integration/jw-pkg/projects/create-pkg-config/test-expected.txt b/test/integration/jw-pkg/projects/create-pkg-config/test-expected.txt new file mode 100644 index 00000000..2a43ccfb --- /dev/null +++ b/test/integration/jw-pkg/projects/create-pkg-config/test-expected.txt @@ -0,0 +1,31 @@ +============= Running: jw-pkg.py -t ../../../../.. --log-level info projects create-pkg-config -n jw-pkg -s jw-pkg test -p /usr -v 1.0 --cflags=-I/usr/include --libflags=-L/usr/lib -ljw-pkg -r jw-core >= 1.0, jw-base -R jw-devel +prefix = /usr +exec_prefix = ${prefix} +includedir = ${prefix}/include +libdir = ${exec_prefix}/lib + +Name: jw-pkg +Description: jw-pkg test +Version: 1.0 +Cflags: -I/usr/include +Libs: -L/usr/lib -ljw-pkg +Requires: jw-core >= 1.0, jw-base +Requires.private: jw-devel============= Running: jw-pkg.py -t ../../../../.. --log-level info projects create-pkg-config -n jw-pkg -p /usr -v 1.0 -r foo bar>=2.0, baz +prefix = /usr +exec_prefix = ${prefix} +includedir = ${prefix}/include +libdir = ${exec_prefix}/lib + +Name: jw-pkg +Description: +Version: 1.0 +Requires: foo, bar >= 2.0, baz============= Running: jw-pkg.py -t ../../../../.. --log-level info projects create-pkg-config -n jw-pkg -p /usr -v 1.0 -R qux +prefix = /usr +exec_prefix = ${prefix} +includedir = ${prefix}/include +libdir = ${exec_prefix}/lib + +Name: jw-pkg +Description: +Version: 1.0 +Requires.private: qux \ No newline at end of file diff --git a/test/integration/jw-pkg/projects/create-pkg-config/test.sh b/test/integration/jw-pkg/projects/create-pkg-config/test.sh new file mode 100644 index 00000000..16812f54 --- /dev/null +++ b/test/integration/jw-pkg/projects/create-pkg-config/test.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# shellcheck disable=SC2048,SC2086 +# Unquoted $* is intentional — the jw-pkg command line + +export LC_ALL="C" +set -euo pipefail + +jw_pkg_py="$*" + +run() +{ + local log_cmd + # shellcheck disable=SC2001 + log_cmd=$(echo "$*" | sed 's|.*python3[0-9.]*\s\+\(\.\.\/\)*scripts/||') + printf '============= Running: %s\n' "$log_cmd" + "$@" +} + +run $jw_pkg_py projects create-pkg-config \ + -n jw-pkg -s 'jw-pkg test' -p /usr -v 1.0 \ + --cflags=-I/usr/include --libflags=-L/usr/lib\ -ljw-pkg \ + -r 'jw-core >= 1.0, jw-base' -R 'jw-devel' + +run $jw_pkg_py projects create-pkg-config \ + -n jw-pkg -p /usr -v 1.0 -r 'foo bar>=2.0, baz' + +run $jw_pkg_py projects create-pkg-config \ + -n jw-pkg -p /usr -v 1.0 -R 'qux' -- 2.55.0