From 0b0cf998b418145c3f8dc0ed9e72c08e0f625530 Mon Sep 17 00:00:00 2001 From: Todd Gamblin Date: Mon, 19 May 2025 17:05:46 -0700 Subject: [PATCH] allow group_arguments to take a prefix length Signed-off-by: Todd Gamblin --- lib/spack/spack/cmd/__init__.py | 24 +++++++++++++++--------- lib/spack/spack/cmd/pkg.py | 3 ++- lib/spack/spack/test/cmd/pkg.py | 8 ++++---- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/lib/spack/spack/cmd/__init__.py b/lib/spack/spack/cmd/__init__.py index 8a2351b412f..99ac123ede2 100644 --- a/lib/spack/spack/cmd/__init__.py +++ b/lib/spack/spack/cmd/__init__.py @@ -705,7 +705,11 @@ def first_line(docstring): def group_arguments( - args: Sequence[str], max_group_size: int = 500, max_group_len: Optional[int] = None + args: Sequence[str], + *, + max_group_size: int = 500, + prefix_length: int = 0, + max_group_length: Optional[int] = None, ) -> Generator[List[str], None, None]: """Splits the supplied list of arguments into groups for passing to CLI tools. @@ -722,30 +726,32 @@ def group_arguments( Arguments: args: list of arguments to split into groups max_group_size: max number of elements in any group (default 500) - max_group_len: max length of characters that if a group of args is joined by " " + prefix_length: length of any additional arguments to be passed before the groups from args; + defaults to 0 characters. + max_group_length: max length of characters that if a group of args is joined by " " On unix, ths defaults to SC_ARG_MAX from sysconf. On Windows the default is the max usable for CreateProcess (32,768 chars) """ - if max_group_len is None: - max_group_len = 32768 # default to the Windows limit + if max_group_length is None: + max_group_length = 32768 # default to the Windows limit if hasattr(os, "sysconf"): # sysconf is only on unix and returns -1 if an option isn't present sysconf_max = os.sysconf("SC_ARG_MAX") if sysconf_max != -1: - max_group_len = sysconf_max + max_group_length = sysconf_max group: List[str] = [] - grouplen, space = 0, 0 + grouplen, space = prefix_length, 0 for i, arg in enumerate(args): arglen = len(arg) - if arglen > max_group_len: + if arglen > max_group_length: raise ValueError(f"Argument is longer than the maximum command line size: '{arg}'") next_grouplen = grouplen + arglen + space - if len(group) == max_group_size or next_grouplen > max_group_len: + if len(group) == max_group_size or next_grouplen > max_group_length: yield group - group, grouplen, space = [], 0, 0 + group, grouplen, space = [], prefix_length, 0 group.append(arg) grouplen += arglen + space diff --git a/lib/spack/spack/cmd/pkg.py b/lib/spack/spack/cmd/pkg.py index 70387563fd0..b8e51522055 100644 --- a/lib/spack/spack/cmd/pkg.py +++ b/lib/spack/spack/cmd/pkg.py @@ -186,7 +186,8 @@ def pkg_grep(args, unknown_args): return 0 # no packages to search # set up iterator and save the first group to ensure we don't end up with a group of size 1 - groups = spack.cmd.group_arguments(all_paths) + prefix_length = len(" ".join(args.grep_args + unknown_args)) + groups = spack.cmd.group_arguments(all_paths, prefix_length=prefix_length) # You can force GNU grep to show filenames on every line with -H, but not POSIX grep. # POSIX grep only shows filenames when you're grepping 2 or more files. Since we diff --git a/lib/spack/spack/test/cmd/pkg.py b/lib/spack/spack/test/cmd/pkg.py index 53d587603bb..12236efe8c2 100644 --- a/lib/spack/spack/test/cmd/pkg.py +++ b/lib/spack/spack/test/cmd/pkg.py @@ -322,7 +322,7 @@ def test_pkg_hash(mock_packages): @pytest.mark.parametrize( - ["max_group_size", "max_group_len", "lengths", "error"], + ["max_group_size", "max_group_length", "lengths", "error"], [ (3, 1, None, ValueError), (3, 13, None, ValueError), @@ -334,9 +334,9 @@ def test_pkg_hash(mock_packages): (4, 56, [4, 4, 2], None), ], ) -def test_group_arguments(mock_packages, max_group_size, max_group_len, lengths, error): +def test_group_arguments(mock_packages, max_group_size, max_group_length, lengths, error): generator = spack.cmd.group_arguments( - group_args, max_group_size=max_group_size, max_group_len=max_group_len + group_args, max_group_size=max_group_size, max_group_length=max_group_length ) # just check that error cases raise @@ -349,7 +349,7 @@ def test_group_arguments(mock_packages, max_group_size, max_group_len, lengths, assert sum(groups, []) == group_args assert [len(group) for group in groups] == lengths assert all( - sum(len(elt) for elt in group) + (len(group) - 1) <= max_group_len for group in groups + sum(len(elt) for elt in group) + (len(group) - 1) <= max_group_length for group in groups )