{"thread":{"id":"65533","subject":"[PATCH] generate-configlist: collapse depfile for older Ninja","startedAt":"2026-04-21T19:17:56Z","lastAt":"2026-05-21T09:31:25Z","messageCount":12,"participants":["Toon Claes","Patrick Steinhardt","Phillip Wood","D. Ben Knoble"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"542052","messageId":"20260421-toon-fix-almalinux8-v1-1-aec1d54addde@iotcl.com","threadId":"65533","inReplyTo":null,"subject":"[PATCH] generate-configlist: collapse depfile for older Ninja","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-04-21T19:17:25Z","receivedAt":"2026-04-21T19:17:56Z","isPatch":true,"body":"The tools/generate-configlist.sh script generates two files:\n  * config-list.h\n  * config-list.h.d\n\nThe former is included by the source code and the latter defines on\nwhich files the former depends.\n\nThe contents of `config-list.h.d` consists of two sections:\n\n    config-list.h: Documentation/config.adoc\n    config-list.h: Documentation/git-config.adoc\n    config-list.h: Documentation/config/add.adoc\n    config-list.h: Documentation/config/advice.adoc\n    config-list.h: Documentation/config/alias.adoc\n    config-list.h: Documentation/config/am.adoc\n    config-list.h: Documentation/config/apply.adoc\n    ...\n\nThis first section actually defines on which individual files\n`config-list.h` depends and thus needs to be rebuild if one of those\nchanges.\n\nAnd the second section contains content like:\n\n    Documentation/config.adoc:\n    Documentation/git-config.adoc:\n    Documentation/config/add.adoc:\n    Documentation/config/advice.adoc:\n    Documentation/config/alias.adoc:\n    Documentation/config/am.adoc:\n    Documentation/config/apply.adoc:\n    ...\n\nThese rules exist to ensure Make won't fail with the following error if\none of the .adoc files is renamed or removed:\n\n   make: *** No rule to make target 'Documentation/config.adoc', needed by 'config-list.h'.\n\nWith the no-op targets defined in `config-list.h.d`, Make knows there's\nno work to be done to generate these files, so it doesn't error out if\nit doesn't exist.\n\nFor the Makefile build system this works great. And since\nebeea3c471 (build: regenerate config-list.h when Documentation changes,\n2026-02-24) this script is also called from the Meson build system.\nNevertheless, on AlmaLinux 8 the following build failure is seen:\n\n    ninja: error: dependency cycle: config-list.h -> config-list.h\n\nThis version of this distro uses Ninja 1.8.2 and it seems to have some\nissues with the format of the `config-list.h.d` file.\n\nNinja versions before 1.10.0 do not reset the depfile parser state on\nnewlines. This causes issues when the depfile has one dependency per\nline, like we have in `config-list.h.d`:\n\n    config-list.h: Documentation/config.adoc\n    config-list.h: Documentation/config/add.adoc\n\nThe parser only recognizes the first \"config-list.h:\" as a target. On\nsubsequent lines it is still in dependency-parsing mode, so the repeated\noutput name is recorded as an input. This causes the error mentioned\nabove.\n\nThe bug in Ninja is fixed in 1.10, with commit\nninja-build/ninja@1daa7470ab7e (depfile_parser: remove restriction on\nmultiple outputs, 2019-11-20).\n\nTo be compatible with older versions of Ninja, add a fourth optional\nargument to `generate-configlist.sh` that can be empty or \"collapse\".\nWhen this argument is \"collapse\", there is a post-processing step on\n`config-list.h.d` to put the dependencies for `config-list.h` on a\nsingle line, like:\n\n    config-list.h: Documentation/config.adoc Documentation/config/add.adoc ...\n\nThis works around the bug in older versions of Ninja.\n\nIn `meson.build`, the Ninja version is detected to determine if the\n\"collapse\" argument needs to be provided to the script. Thus newer\nversions of Ninja, and the Makefile build system still get dependencies\non separate lines in `config-list.h.d`.\n\nIn this post-processing, also the no-op targets are dropped because they\nare simply not needed for Ninja.\n\nSigned-off-by: Toon Claes <toon@iotcl.com>\n---\nAt GitLab we build images for various distros, including AlmaLinux 8.\nOn this distro we got this error while compiling Git.\n\n    ninja: error: dependency cycle: config-list.h -> config-list.h\n\nIt seems this is caused by a bug in older versions of Ninja. There are\nmore details in the commit message, but here are a few simple steps to\nreproduce:\n\ndocker run --rm -it -v $(pwd):/git -w /git almalinux:8 bash\n    dnf -yq install epel-release\n    dnf -yq install shadow-utils sudo make pkg-config gcc findutils \\\n        diffutils perl python3 gawk gettext zlib-devel expat-devel \\\n        openssl-devel curl-devel pcre2-devel cargo\n    pip3 install --prefix=/usr meson ninja==1.8.2\n    meson setup build --warnlevel 2 --werror\n    ninja -C build config-list.h\n    ninja -C build config-list.h   # fails with dependency cycle\n---\n meson.build                  | 10 ++++++++++\n tools/generate-configlist.sh | 24 +++++++++++++++++++++++-\n 2 files changed, 33 insertions(+), 1 deletion(-)\n\ndiff --git a/meson.build b/meson.build\nindex 11488623bf..44e6f679c3 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -725,6 +725,15 @@ if not get_option('breaking_changes')\n   builtin_sources += 'builtin/pack-redundant.c'\n endif\n \n+configlist_gen_format = ''\n+if get_option('backend') == 'ninja'\n+  ninja = find_program('ninja', 'ninja-build', native: true)\n+  ninja_version = run_command(ninja, '--version', check: true).stdout().strip()\n+  if ninja_version.version_compare('<1.10.0')\n+    configlist_gen_format = 'collapse'\n+  endif\n+endif\n+\n builtin_sources += custom_target(\n   output: 'config-list.h',\n   depfile: 'config-list.h.d',\n@@ -735,6 +744,7 @@ builtin_sources += custom_target(\n     meson.current_source_dir(),\n     '@OUTPUT@',\n     '@DEPFILE@',\n+    configlist_gen_format,\n   ],\n   env: script_environment,\n )\ndiff --git a/tools/generate-configlist.sh b/tools/generate-configlist.sh\nindex e28054f9e0..553fbfeb4b 100755\n--- a/tools/generate-configlist.sh\n+++ b/tools/generate-configlist.sh\n@@ -3,10 +3,14 @@\n SOURCE_DIR=\"$1\"\n OUTPUT=\"$2\"\n DEPFILE=\"$3\"\n+FORMAT=\"${4}\"\n \n if test -z \"$SOURCE_DIR\" || ! test -d \"$SOURCE_DIR\" || test -z \"$OUTPUT\"\n then\n-\techo >&2 \"USAGE: $0 <SOURCE_DIR> <OUTPUT> [<DEPFILE>]\"\n+\techo >&2 \"USAGE: $0 <SOURCE_DIR> <OUTPUT> [<DEPFILE> [collapse]]\"\n+\techo >&2 \"\"\n+\techo >&2 \"  Pass 'collapse' to write all deps on a single line.\"\n+\techo >&2 \"  Ninja < 1.10.0 chokes on depfiles with multiple lines.\"\n \texit 1\n fi\n \n@@ -49,4 +53,22 @@ then\n \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n \t\t\tsed -e 's/[# ]/\\\\&/g'\n \t} >\"$DEPFILE\"\n+\n+\t# Due to a bug in Ninja versions before 1.10.0 the depfile parser state\n+\t# is not reset on newlines, causing the target to be recorded as a\n+\t# dependency of itself when there is one dependency per line.\n+\t# The bug is fixed in ninja-build/ninja@1daa7470ab7e (depfile_parser:\n+\t# remove restriction on multiple outputs, 2019-11-20).\n+\t# But to work around the issue, do a \"collapse\" post-processing step\n+\t# if wanted.\n+\tif test \"$FORMAT\" = \"collapse\"\n+\tthen\n+\t\t{\n+\t\t\tprintf '%s: ' \"$OUTPUT\"\n+\t\t\tsed -n -e \"s/^$QUOTED_OUTPUT: //p\" \"$DEPFILE\" |\n+\t\t\t\ttr '\\n' ' '\n+\t\t\techo\n+\t\t} >\"$DEPFILE.tmp\"\n+\t\tmv \"$DEPFILE.tmp\" \"$DEPFILE\"\n+\tfi\n fi\n\n---\nbase-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\nchange-id: 20260421-toon-fix-almalinux8-102de9138294\n\n"},{"id":"542096","messageId":"aehsikCfPm83M9dN@pks.im","threadId":"65533","inReplyTo":"20260421-toon-fix-almalinux8-v1-1-aec1d54addde@iotcl.com","subject":"Re: [PATCH] generate-configlist: collapse depfile for older Ninja","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-04-22T06:36:58Z","receivedAt":"2026-04-22T06:37:12Z","isPatch":true,"body":"On Tue, Apr 21, 2026 at 09:17:25PM +0200, Toon Claes wrote:\n> For the Makefile build system this works great. And since\n> ebeea3c471 (build: regenerate config-list.h when Documentation changes,\n> 2026-02-24) this script is also called from the Meson build system.\n> Nevertheless, on AlmaLinux 8 the following build failure is seen:\n> \n>     ninja: error: dependency cycle: config-list.h -> config-list.h\n> \n> This version of this distro uses Ninja 1.8.2 and it seems to have some\n> issues with the format of the `config-list.h.d` file.\n\nRight, I can reproduce the issue with that version indeed:\n\n    $ meson setup -Ddocs=man build\n    $ ninja -C build\n    ... successful build ...\n    $ ninja -C build\n    ninja: error: dependency cycle: config-list.h -> config-list.h\n\n> Ninja versions before 1.10.0 do not reset the depfile parser state on\n> newlines. This causes issues when the depfile has one dependency per\n> line, like we have in `config-list.h.d`:\n> \n>     config-list.h: Documentation/config.adoc\n>     config-list.h: Documentation/config/add.adoc\n> \n> The parser only recognizes the first \"config-list.h:\" as a target. On\n> subsequent lines it is still in dependency-parsing mode, so the repeated\n> output name is recorded as an input. This causes the error mentioned\n> above.\n> \n> The bug in Ninja is fixed in 1.10, with commit\n> ninja-build/ninja@1daa7470ab7e (depfile_parser: remove restriction on\n> multiple outputs, 2019-11-20).\n\nGood find.\n\n> To be compatible with older versions of Ninja, add a fourth optional\n> argument to `generate-configlist.sh` that can be empty or \"collapse\".\n> When this argument is \"collapse\", there is a post-processing step on\n> `config-list.h.d` to put the dependencies for `config-list.h` on a\n> single line, like:\n> \n>     config-list.h: Documentation/config.adoc Documentation/config/add.adoc ...\n> \n> This works around the bug in older versions of Ninja.\n\nDo we really have to make the logic conditional? I would expect that\nthis works just fine for newer versions of Ninja and for Make, so it\nfeels rather pointless to me to have two modes to worry about.\n\nSo wouldn't the below simplified version be sufficient?\n\nThanks!\n\nPatrick\n\ndiff --git a/tools/generate-configlist.sh b/tools/generate-configlist.sh\nindex e28054f9e0..f5f42492c6 100755\n--- a/tools/generate-configlist.sh\n+++ b/tools/generate-configlist.sh\n@@ -44,7 +44,9 @@ then\n \t{\n \t\tprintf '%s\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n-\t\t\tsed -e 's/[# ]/\\\\&/g' -e \"s/^/$QUOTED_OUTPUT: /\"\n+\t\t\tsed -e 's/[# ]/\\\\&/g' |\n+\t\t\ttr '\\n' ' ' |\n+\t\t\tsed -e \"s/^/$QUOTED_OUTPUT: /\" -e 's/ $/\\n/'\n \t\tprintf '%s:\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n \t\t\tsed -e 's/[# ]/\\\\&/g'\n"},{"id":"542098","messageId":"878qafeait.fsf@toon--20250203-5JQV3.mail-host-address-is-not-set","threadId":"65533","inReplyTo":"aehsikCfPm83M9dN@pks.im","subject":"Re: [PATCH] generate-configlist: collapse depfile for older Ninja","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-04-22T07:09:46Z","receivedAt":"2026-04-22T07:09:55Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Do we really have to make the logic conditional? I would expect that\n> this works just fine for newer versions of Ninja and for Make, so it\n> feels rather pointless to me to have two modes to worry about.\n>\n> So wouldn't the below simplified version be sufficient?\n\nI considered that, but I wasn't sure. I went all-in to show what it\ncould be, but I'm happy to see simplifications.\n\nNow your version doesn't remove the no-op targets from config-list.h.d.\nI did some testing, and while Ninja doesn't need those no-op target,\nthey are also harmless to keep them. Thus I agree your version is\nsimpler and better. Let me roll another version.\n\nThanks!\n\n> diff --git a/tools/generate-configlist.sh b/tools/generate-configlist.sh\n> index e28054f9e0..f5f42492c6 100755\n> --- a/tools/generate-configlist.sh\n> +++ b/tools/generate-configlist.sh\n> @@ -44,7 +44,9 @@ then\n>  \t{\n>  \t\tprintf '%s\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n>  \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n> -\t\t\tsed -e 's/[# ]/\\\\&/g' -e \"s/^/$QUOTED_OUTPUT: /\"\n> +\t\t\tsed -e 's/[# ]/\\\\&/g' |\n> +\t\t\ttr '\\n' ' ' |\n> +\t\t\tsed -e \"s/^/$QUOTED_OUTPUT: /\" -e 's/ $/\\n/'\n>  \t\tprintf '%s:\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n>  \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n>  \t\t\tsed -e 's/[# ]/\\\\&/g'\n\n-- \nCheers,\nToon\n"},{"id":"542099","messageId":"20260422-toon-fix-almalinux8-v2-1-45d8471ed0e9@iotcl.com","threadId":"65533","inReplyTo":"20260421-toon-fix-almalinux8-v1-1-aec1d54addde@iotcl.com","subject":"[PATCH v2] generate-configlist: collapse depfile for older Ninja","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-04-22T07:21:20Z","receivedAt":"2026-04-22T07:21:28Z","isPatch":true,"body":"The tools/generate-configlist.sh script generates two files:\n  * config-list.h\n  * config-list.h.d\n\nThe former is included by the source code and the latter defines on\nwhich files the former depends.\n\nThe contents of `config-list.h.d` consists of two sections:\n\n    config-list.h: Documentation/config.adoc\n    config-list.h: Documentation/git-config.adoc\n    config-list.h: Documentation/config/add.adoc\n    config-list.h: Documentation/config/advice.adoc\n    config-list.h: Documentation/config/alias.adoc\n    config-list.h: Documentation/config/am.adoc\n    config-list.h: Documentation/config/apply.adoc\n    ...\n\nThis first section actually defines on which individual files\n`config-list.h` depends and thus needs to be rebuild if one of those\nchanges.\n\nAnd the second section contains content like:\n\n    Documentation/config.adoc:\n    Documentation/git-config.adoc:\n    Documentation/config/add.adoc:\n    Documentation/config/advice.adoc:\n    Documentation/config/alias.adoc:\n    Documentation/config/am.adoc:\n    Documentation/config/apply.adoc:\n    ...\n\nThese rules exist to ensure Make won't fail with the following error if\none of the .adoc files is renamed or removed:\n\n   make: *** No rule to make target 'Documentation/config.adoc', needed by 'config-list.h'.\n\nWith the no-op targets defined in `config-list.h.d`, Make knows there's\nno work to be done to generate these files, so it doesn't error out if\nit doesn't exist.\n\nFor the Makefile build system this works great. And since\nebeea3c471 (build: regenerate config-list.h when Documentation changes,\n2026-02-24) this script is also called from the Meson build system.\nNevertheless, on AlmaLinux 8 the following build failure is seen:\n\n    ninja: error: dependency cycle: config-list.h -> config-list.h\n\nThis version of this distro uses Ninja 1.8.2 and it seems to have some\nissues with the format of the `config-list.h.d` file.\n\nNinja versions before 1.10.0 do not reset the depfile parser state on\nnewlines. This causes issues when the depfile has one dependency per\nline, like we have in `config-list.h.d`:\n\n    config-list.h: Documentation/config.adoc\n    config-list.h: Documentation/config/add.adoc\n\nThe parser only recognizes the first \"config-list.h:\" as a target. On\nsubsequent lines it is still in dependency-parsing mode, so the repeated\noutput name is recorded as an input. This causes the error mentioned\nabove.\n\nThe bug in Ninja is fixed in 1.10, with commit\nninja-build/ninja@1daa7470ab7e (depfile_parser: remove restriction on\nmultiple outputs, 2019-11-20).\n\nTo be compatible with older versions of Ninja, collapse the dependencies\nfor `config-list.h` into a single line like:\n\n    config-list.h: Documentation/config.adoc Documentation/config/add.adoc ...\n\nThis works around the bug in older versions of Ninja, and is fully\ncompatible Make and with more recent versions of Ninja. And while the\nno-op targets are not needed for Ninja, they also don't do any harm.\n\nHelped-by: Patrick Steinhardt <ps@pks.im>\nSigned-off-by: Toon Claes <toon@iotcl.com>\n---\nAt GitLab we build images for various distros, including AlmaLinux 8.\nOn this distro we got this error while compiling Git.\n\n    ninja: error: dependency cycle: config-list.h -> config-list.h\n\nIt seems this is caused by a bug in older versions of Ninja. There are\nmore details in the commit message, but here are a few simple steps to\nreproduce:\n\ndocker run --rm -it -v $(pwd):/git -w /git almalinux:8 bash\n    dnf -yq install epel-release\n    dnf -yq install shadow-utils sudo make pkg-config gcc findutils \\\n        diffutils perl python3 gawk gettext zlib-devel expat-devel \\\n        openssl-devel curl-devel pcre2-devel cargo\n    pip3 install --prefix=/usr meson ninja==1.8.2\n    meson setup build --warnlevel 2 --werror\n    ninja -C build config-list.h\n    ninja -C build config-list.h   # fails with dependency cycle\n---\nChanges in v2:\n- Simplify the changes *a lot* by doing the collapsing unconditionally.\n- Link to v1: https://patch.msgid.link/20260421-toon-fix-almalinux8-v1-1-aec1d54addde@iotcl.com\n---\n tools/generate-configlist.sh | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/tools/generate-configlist.sh b/tools/generate-configlist.sh\nindex e28054f9e0..f5f42492c6 100755\n--- a/tools/generate-configlist.sh\n+++ b/tools/generate-configlist.sh\n@@ -44,7 +44,9 @@ then\n \t{\n \t\tprintf '%s\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n-\t\t\tsed -e 's/[# ]/\\\\&/g' -e \"s/^/$QUOTED_OUTPUT: /\"\n+\t\t\tsed -e 's/[# ]/\\\\&/g' |\n+\t\t\ttr '\\n' ' ' |\n+\t\t\tsed -e \"s/^/$QUOTED_OUTPUT: /\" -e 's/ $/\\n/'\n \t\tprintf '%s:\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n \t\t\tsed -e 's/[# ]/\\\\&/g'\n\n---\nbase-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\nchange-id: 20260421-toon-fix-almalinux8-102de9138294\n\n"},{"id":"542105","messageId":"aeijSwIcqzkqstFG@pks.im","threadId":"65533","inReplyTo":"20260422-toon-fix-almalinux8-v2-1-45d8471ed0e9@iotcl.com","subject":"Re: [PATCH v2] generate-configlist: collapse depfile for older Ninja","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-04-22T10:30:35Z","receivedAt":"2026-04-22T10:30:42Z","isPatch":true,"body":"On Wed, Apr 22, 2026 at 09:21:20AM +0200, Toon Claes wrote:\n> Changes in v2:\n> - Simplify the changes *a lot* by doing the collapsing unconditionally.\n> - Link to v1: https://patch.msgid.link/20260421-toon-fix-almalinux8-v1-1-aec1d54addde@iotcl.com\n\nThanks. I've tested those changes with Ninja 1.8.2 and can confirm that\nit does fix the issue.\n\nPatrick\n"},{"id":"542131","messageId":"0557838b-214d-4e8f-9cbd-bc342563e9ba@gmail.com","threadId":"65533","inReplyTo":"20260422-toon-fix-almalinux8-v2-1-45d8471ed0e9@iotcl.com","subject":"Re: [PATCH v2] generate-configlist: collapse depfile for older Ninja","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-22T13:45:45Z","receivedAt":"2026-04-22T13:45:48Z","isPatch":true,"body":"Hi Toon\n\nOn 22/04/2026 08:21, Toon Claes wrote:\n\nThanks for the excellent commit message which I've trimmed.\n\n> diff --git a/tools/generate-configlist.sh b/tools/generate-configlist.sh\n> index e28054f9e0..f5f42492c6 100755\n> --- a/tools/generate-configlist.sh\n> +++ b/tools/generate-configlist.sh\n> @@ -44,7 +44,9 @@ then\n>   \t{\n>   \t\tprintf '%s\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n>   \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n> -\t\t\tsed -e 's/[# ]/\\\\&/g' -e \"s/^/$QUOTED_OUTPUT: /\"\n> +\t\t\tsed -e 's/[# ]/\\\\&/g' |\n> +\t\t\ttr '\\n' ' ' |\n> +\t\t\tsed -e \"s/^/$QUOTED_OUTPUT: /\" -e 's/ $/\\n/'\n\nI don't think this use of '\\n' portable. The sed man page [1] says that \n'\\n' matches a newline in the pattern space, but does not mention it \nbeing supported in the replacement string. We do have an existing use in \nt4150-am.sh:\"am newline in subject\" which does\n\n\tsed -e \"s/second/second \\\\\\n foo/\" patch1 >patchnl &&\n\nHowever if I add \"cat patchnl\" it shows the subject line is\n\n\tSubject: [PATCH] second \\n foo\n\nso sed has inserted \"\\n\" rather than a newline. Indeed looking at the \ncommit message for that test it is testing a fix that c escapes are \nprinted verbatim introduced by 4b7cc26a74 (git-am: use printf instead of \necho on user-supplied strings, 2007-05-25).\n\nI've not tested it but I think\n\n\tsed 's/ $/\\\n/'\n\nwill insert a newline. Alternatively we could do\n\n\tprintf '%s' \"$QUOTED_OUTPUT: \"\n\tprintf '%s\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n  \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n\t\tsed -e 's/[# ]/\\\\&/g' |\n\t\ttr '\\n' ' '\n\tprintf '\\n'\n\nThat leaves a trailing space at the end of the line but I don't think \nthat should matter.\n\nAs I recall, the depfiles created by gcc have all the dependencies on a \nsingle line so this should be widely supported and I agree with Patrick \nthat we should do this unconditionally.\n\nThanks\n\nPhillip\n\n[1] https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html\n\n>   \t\tprintf '%s:\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n>   \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n>   \t\t\tsed -e 's/[# ]/\\\\&/g'\n> \n> ---\n> base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\n> change-id: 20260421-toon-fix-almalinux8-102de9138294\n> \n> \n\n"},{"id":"542132","messageId":"de2b6af2-b1ba-4262-a171-22c9cdbc7bee@gmail.com","threadId":"65533","inReplyTo":"0557838b-214d-4e8f-9cbd-bc342563e9ba@gmail.com","subject":"Re: [PATCH v2] generate-configlist: collapse depfile for older Ninja","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-22T14:12:39Z","receivedAt":"2026-04-22T14:12:43Z","isPatch":true,"body":"On 22/04/2026 14:45, Phillip Wood wrote:\n> \n>      sed -e \"s/second/second \\\\\\n foo/\" patch1 >patchnl &&\n\nOf course the extra backslashes would supress any special meaning of \n'\\n' so that's not a good example. However the freebsd man page [1] says\n\n    The\tescape sequence\t\\n matches a newline character embedded\tin the\n    pattern space. You cannot, however, use a literal newline character\n    in an address or in the substitute command.\n\nThanks\n\nPhillip\n\n[1] \nhttps://man.freebsd.org/cgi/man.cgi?query=sed&apropos=0&sektion=0&manpath=FreeBSD+16.0-CURRENT&format=html\n\n> \n> However if I add \"cat patchnl\" it shows the subject line is\n> \n>      Subject: [PATCH] second \\n foo\n> \n> so sed has inserted \"\\n\" rather than a newline. Indeed looking at the \n> commit message for that test it is testing a fix that c escapes are \n> printed verbatim introduced by 4b7cc26a74 (git-am: use printf instead of \n> echo on user-supplied strings, 2007-05-25).\n> \n> I've not tested it but I think\n> \n>      sed 's/ $/\\\n> /'\n> \n> will insert a newline. Alternatively we could do\n> \n>      printf '%s' \"$QUOTED_OUTPUT: \"\n>      printf '%s\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n>               \"$SOURCE_DIR\"/Documentation/config/*.adoc |\n>          sed -e 's/[# ]/\\\\&/g' |\n>          tr '\\n' ' '\n>      printf '\\n'\n> \n> That leaves a trailing space at the end of the line but I don't think \n> that should matter.\n> \n> As I recall, the depfiles created by gcc have all the dependencies on a \n> single line so this should be widely supported and I agree with Patrick \n> that we should do this unconditionally.\n> \n> Thanks\n> \n> Phillip\n> \n> [1] https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html\n> \n>>           printf '%s:\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n>>               \"$SOURCE_DIR\"/Documentation/config/*.adoc |\n>>               sed -e 's/[# ]/\\\\&/g'\n>>\n>> ---\n>> base-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\n>> change-id: 20260421-toon-fix-almalinux8-102de9138294\n>>\n>>\n> \n\n"},{"id":"542153","messageId":"CALnO6CDM3HGcJgKWAqVMRXw=HWbW+xA+FNwDMJwkuf8AoSmv1w@mail.gmail.com","threadId":"65533","inReplyTo":"20260421-toon-fix-almalinux8-v1-1-aec1d54addde@iotcl.com","subject":"Re: [PATCH] generate-configlist: collapse depfile for older Ninja","fromName":"D. Ben Knoble","fromEmail":"ben.knoble+github@gmail.com","sentAt":"2026-04-22T18:35:41Z","receivedAt":"2026-04-22T18:35:54Z","isPatch":true,"body":"On Tue, Apr 21, 2026 at 3:17 PM Toon Claes <toon@iotcl.com> wrote:\n>\n> The tools/generate-configlist.sh script generates two files:\n>   * config-list.h\n>   * config-list.h.d\n>\n> The former is included by the source code and the latter defines on\n> which files the former depends.\n>\n> The contents of `config-list.h.d` consists of two sections:\n>\n>     config-list.h: Documentation/config.adoc\n>     config-list.h: Documentation/git-config.adoc\n>     config-list.h: Documentation/config/add.adoc\n>     config-list.h: Documentation/config/advice.adoc\n>     config-list.h: Documentation/config/alias.adoc\n>     config-list.h: Documentation/config/am.adoc\n>     config-list.h: Documentation/config/apply.adoc\n>     ...\n>\n> This first section actually defines on which individual files\n> `config-list.h` depends and thus needs to be rebuild if one of those\n> changes.\n>\n> And the second section contains content like:\n>\n>     Documentation/config.adoc:\n>     Documentation/git-config.adoc:\n>     Documentation/config/add.adoc:\n>     Documentation/config/advice.adoc:\n>     Documentation/config/alias.adoc:\n>     Documentation/config/am.adoc:\n>     Documentation/config/apply.adoc:\n>     ...\n>\n> These rules exist to ensure Make won't fail with the following error if\n> one of the .adoc files is renamed or removed:\n>\n>    make: *** No rule to make target 'Documentation/config.adoc', needed by 'config-list.h'.\n>\n> With the no-op targets defined in `config-list.h.d`, Make knows there's\n> no work to be done to generate these files, so it doesn't error out if\n> it doesn't exist.\n>\n> For the Makefile build system this works great. And since\n> ebeea3c471 (build: regenerate config-list.h when Documentation changes,\n> 2026-02-24) this script is also called from the Meson build system.\n> Nevertheless, on AlmaLinux 8 the following build failure is seen:\n>\n>     ninja: error: dependency cycle: config-list.h -> config-list.h\n>\n> This version of this distro uses Ninja 1.8.2 and it seems to have some\n> issues with the format of the `config-list.h.d` file.\n>\n> Ninja versions before 1.10.0 do not reset the depfile parser state on\n> newlines. This causes issues when the depfile has one dependency per\n> line, like we have in `config-list.h.d`:\n>\n>     config-list.h: Documentation/config.adoc\n>     config-list.h: Documentation/config/add.adoc\n>\n> The parser only recognizes the first \"config-list.h:\" as a target. On\n> subsequent lines it is still in dependency-parsing mode, so the repeated\n> output name is recorded as an input. This causes the error mentioned\n> above.\n>\n> The bug in Ninja is fixed in 1.10, with commit\n> ninja-build/ninja@1daa7470ab7e (depfile_parser: remove restriction on\n> multiple outputs, 2019-11-20).\n\nFascinating. Thanks for finding and fixing. I did wish while embarking\non this endeavor to find more documentation of what these depfiles\nshould look like, so I'm not surprised to find some bugs in how they\nare parsed.\n"},{"id":"543388","messageId":"20260515-toon-fix-almalinux8-v3-1-b545a0647f0f@iotcl.com","threadId":"65533","inReplyTo":"20260422-toon-fix-almalinux8-v2-1-45d8471ed0e9@iotcl.com","subject":"[PATCH v3] generate-configlist: collapse depfile for older Ninja","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-05-15T08:42:26Z","receivedAt":"2026-05-15T08:42:41Z","isPatch":true,"body":"The tools/generate-configlist.sh script generates two files:\n  * config-list.h\n  * config-list.h.d\n\nThe former is included by the source code and the latter defines on\nwhich files the former depends.\n\nThe contents of `config-list.h.d` consists of two sections:\n\n    config-list.h: Documentation/config.adoc\n    config-list.h: Documentation/git-config.adoc\n    config-list.h: Documentation/config/add.adoc\n    config-list.h: Documentation/config/advice.adoc\n    config-list.h: Documentation/config/alias.adoc\n    config-list.h: Documentation/config/am.adoc\n    config-list.h: Documentation/config/apply.adoc\n    ...\n\nThis first section actually defines on which individual files\n`config-list.h` depends and thus needs to be rebuild if one of those\nchanges.\n\nAnd the second section contains content like:\n\n    Documentation/config.adoc:\n    Documentation/git-config.adoc:\n    Documentation/config/add.adoc:\n    Documentation/config/advice.adoc:\n    Documentation/config/alias.adoc:\n    Documentation/config/am.adoc:\n    Documentation/config/apply.adoc:\n    ...\n\nThese rules exist to ensure Make won't fail with the following error if\none of the .adoc files is renamed or removed:\n\n   make: *** No rule to make target 'Documentation/config.adoc', needed by 'config-list.h'.\n\nWith the no-op targets defined in `config-list.h.d`, Make knows there's\nno work to be done to generate these files, so it doesn't error out if\nit doesn't exist.\n\nFor the Makefile build system this works great. And since\nebeea3c471 (build: regenerate config-list.h when Documentation changes,\n2026-02-24) this script is also called from the Meson build system.\nNevertheless, on AlmaLinux 8 the following build failure is seen:\n\n    ninja: error: dependency cycle: config-list.h -> config-list.h\n\nThis version of this distro uses Ninja 1.8.2 and it seems to have some\nissues with the format of the `config-list.h.d` file.\n\nNinja versions before 1.10.0 do not reset the depfile parser state on\nnewlines. This causes issues when the depfile has one dependency per\nline, like we have in `config-list.h.d`:\n\n    config-list.h: Documentation/config.adoc\n    config-list.h: Documentation/config/add.adoc\n\nThe parser only recognizes the first \"config-list.h:\" as a target. On\nsubsequent lines it is still in dependency-parsing mode, so the repeated\noutput name is recorded as an input. This causes the error mentioned\nabove.\n\nThe bug in Ninja is fixed in 1.10, with commit\nninja-build/ninja@1daa7470ab7e (depfile_parser: remove restriction on\nmultiple outputs, 2019-11-20).\n\nTo be compatible with older versions of Ninja, collapse the dependencies\nfor `config-list.h` into a single line like:\n\n    config-list.h: Documentation/config.adoc Documentation/config/add.adoc ...\n\nThis works around the bug in older versions of Ninja, and is fully\ncompatible Make and with more recent versions of Ninja. And while the\nno-op targets are not needed for Ninja, they also don't do any harm.\n\nHelped-by: Patrick Steinhardt <ps@pks.im>\nSigned-off-by: Toon Claes <toon@iotcl.com>\n---\nAt GitLab we build images for various distros, including AlmaLinux 8.\nOn this distro we got this error while compiling Git.\n\n    ninja: error: dependency cycle: config-list.h -> config-list.h\n\nIt seems this is caused by a bug in older versions of Ninja. There are\nmore details in the commit message, but here are a few simple steps to\nreproduce:\n\ndocker run --rm -it -v $(pwd):/git -w /git almalinux:8 bash\n    dnf -yq install epel-release\n    dnf -yq install shadow-utils sudo make pkg-config gcc findutils \\\n        diffutils perl python3 gawk gettext zlib-devel expat-devel \\\n        openssl-devel curl-devel pcre2-devel cargo\n    pip3 install --prefix=/usr meson ninja==1.8.2\n    meson setup build --warnlevel 2 --werror\n    ninja -C build config-list.h\n    ninja -C build config-list.h   # fails with dependency cycle\n---\nChanges in v3:\n- Stop using \\n in sed(1) replacement strings because it is not\n  portable.\n- Link to v2: https://patch.msgid.link/20260422-toon-fix-almalinux8-v2-1-45d8471ed0e9@iotcl.com\n\nChanges in v2:\n- Simplify the changes *a lot* by doing the collapsing unconditionally.\n- Link to v1: https://patch.msgid.link/20260421-toon-fix-almalinux8-v1-1-aec1d54addde@iotcl.com\n---\n tools/generate-configlist.sh | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/tools/generate-configlist.sh b/tools/generate-configlist.sh\nindex e28054f9e0..d1d2ba4bb7 100755\n--- a/tools/generate-configlist.sh\n+++ b/tools/generate-configlist.sh\n@@ -42,9 +42,12 @@ if test -n \"$DEPFILE\"\n then\n \tQUOTED_OUTPUT=\"$(printf '%s\\n' \"$OUTPUT\" | sed 's,[&/\\],\\\\&,g')\"\n \t{\n+\t\tprintf '%s' \"$QUOTED_OUTPUT: \"\n \t\tprintf '%s\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n-\t\t\tsed -e 's/[# ]/\\\\&/g' -e \"s/^/$QUOTED_OUTPUT: /\"\n+\t\t\tsed -e 's/[# ]/\\\\&/g' |\n+\t\t\ttr '\\n' ' '\n+\t\tprintf '\\n'\n \t\tprintf '%s:\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n \t\t\tsed -e 's/[# ]/\\\\&/g'\n\n---\nbase-commit: 59ff4886a579f4bc91e976fe18590b9ae02c7a08\nchange-id: 20260421-toon-fix-almalinux8-102de9138294\n\n"},{"id":"543389","messageId":"87wlx59i4n.fsf@emacs.iotcl.com","threadId":"65533","inReplyTo":"0557838b-214d-4e8f-9cbd-bc342563e9ba@gmail.com","subject":"Re: [PATCH v2] generate-configlist: collapse depfile for older Ninja","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-05-15T08:44:40Z","receivedAt":"2026-05-15T08:44:48Z","isPatch":true,"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> I don't think this use of '\\n' portable. The sed man page [1] says that \n> '\\n' matches a newline in the pattern space, but does not mention it \n> being supported in the replacement string. We do have an existing use in \n> t4150-am.sh:\"am newline in subject\" which does\n>\n> \tsed -e \"s/second/second \\\\\\n foo/\" patch1 >patchnl &&\n>\n> However if I add \"cat patchnl\" it shows the subject line is\n>\n> \tSubject: [PATCH] second \\n foo\n>\n> so sed has inserted \"\\n\" rather than a newline. Indeed looking at the \n> commit message for that test it is testing a fix that c escapes are \n> printed verbatim introduced by 4b7cc26a74 (git-am: use printf instead of \n> echo on user-supplied strings, 2007-05-25).\n>\n> I've not tested it but I think\n>\n> \tsed 's/ $/\\\n> /'\n>\n> will insert a newline. Alternatively we could do\n>\n> \tprintf '%s' \"$QUOTED_OUTPUT: \"\n> \tprintf '%s\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n>   \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n> \t\tsed -e 's/[# ]/\\\\&/g' |\n> \t\ttr '\\n' ' '\n> \tprintf '\\n'\n\nWhoops, I somehow archived your reply without addressing it. Thanks for\nfinding this. I've sent out v3 with this suggestion.\n\n--\nToon\n\n"},{"id":"543392","messageId":"e32f559e-0b28-40be-875b-956de2bb2fca@gmail.com","threadId":"65533","inReplyTo":"20260515-toon-fix-almalinux8-v3-1-b545a0647f0f@iotcl.com","subject":"Re: [PATCH v3] generate-configlist: collapse depfile for older Ninja","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-05-15T09:35:32Z","receivedAt":"2026-05-15T09:35:36Z","isPatch":true,"body":"Hi Toon\n\nThanks for re-rolling, this version looks good to me\n\nPhillip\n\nOn 15/05/2026 09:42, Toon Claes wrote:\n> The tools/generate-configlist.sh script generates two files:\n>    * config-list.h\n>    * config-list.h.d\n> \n> The former is included by the source code and the latter defines on\n> which files the former depends.\n> \n> The contents of `config-list.h.d` consists of two sections:\n> \n>      config-list.h: Documentation/config.adoc\n>      config-list.h: Documentation/git-config.adoc\n>      config-list.h: Documentation/config/add.adoc\n>      config-list.h: Documentation/config/advice.adoc\n>      config-list.h: Documentation/config/alias.adoc\n>      config-list.h: Documentation/config/am.adoc\n>      config-list.h: Documentation/config/apply.adoc\n>      ...\n> \n> This first section actually defines on which individual files\n> `config-list.h` depends and thus needs to be rebuild if one of those\n> changes.\n> \n> And the second section contains content like:\n> \n>      Documentation/config.adoc:\n>      Documentation/git-config.adoc:\n>      Documentation/config/add.adoc:\n>      Documentation/config/advice.adoc:\n>      Documentation/config/alias.adoc:\n>      Documentation/config/am.adoc:\n>      Documentation/config/apply.adoc:\n>      ...\n> \n> These rules exist to ensure Make won't fail with the following error if\n> one of the .adoc files is renamed or removed:\n> \n>     make: *** No rule to make target 'Documentation/config.adoc', needed by 'config-list.h'.\n> \n> With the no-op targets defined in `config-list.h.d`, Make knows there's\n> no work to be done to generate these files, so it doesn't error out if\n> it doesn't exist.\n> \n> For the Makefile build system this works great. And since\n> ebeea3c471 (build: regenerate config-list.h when Documentation changes,\n> 2026-02-24) this script is also called from the Meson build system.\n> Nevertheless, on AlmaLinux 8 the following build failure is seen:\n> \n>      ninja: error: dependency cycle: config-list.h -> config-list.h\n> \n> This version of this distro uses Ninja 1.8.2 and it seems to have some\n> issues with the format of the `config-list.h.d` file.\n> \n> Ninja versions before 1.10.0 do not reset the depfile parser state on\n> newlines. This causes issues when the depfile has one dependency per\n> line, like we have in `config-list.h.d`:\n> \n>      config-list.h: Documentation/config.adoc\n>      config-list.h: Documentation/config/add.adoc\n> \n> The parser only recognizes the first \"config-list.h:\" as a target. On\n> subsequent lines it is still in dependency-parsing mode, so the repeated\n> output name is recorded as an input. This causes the error mentioned\n> above.\n> \n> The bug in Ninja is fixed in 1.10, with commit\n> ninja-build/ninja@1daa7470ab7e (depfile_parser: remove restriction on\n> multiple outputs, 2019-11-20).\n> \n> To be compatible with older versions of Ninja, collapse the dependencies\n> for `config-list.h` into a single line like:\n> \n>      config-list.h: Documentation/config.adoc Documentation/config/add.adoc ...\n> \n> This works around the bug in older versions of Ninja, and is fully\n> compatible Make and with more recent versions of Ninja. And while the\n> no-op targets are not needed for Ninja, they also don't do any harm.\n> \n> Helped-by: Patrick Steinhardt <ps@pks.im>\n> Signed-off-by: Toon Claes <toon@iotcl.com>\n> ---\n> At GitLab we build images for various distros, including AlmaLinux 8.\n> On this distro we got this error while compiling Git.\n> \n>      ninja: error: dependency cycle: config-list.h -> config-list.h\n> \n> It seems this is caused by a bug in older versions of Ninja. There are\n> more details in the commit message, but here are a few simple steps to\n> reproduce:\n> \n> docker run --rm -it -v $(pwd):/git -w /git almalinux:8 bash\n>      dnf -yq install epel-release\n>      dnf -yq install shadow-utils sudo make pkg-config gcc findutils \\\n>          diffutils perl python3 gawk gettext zlib-devel expat-devel \\\n>          openssl-devel curl-devel pcre2-devel cargo\n>      pip3 install --prefix=/usr meson ninja==1.8.2\n>      meson setup build --warnlevel 2 --werror\n>      ninja -C build config-list.h\n>      ninja -C build config-list.h   # fails with dependency cycle\n> ---\n> Changes in v3:\n> - Stop using \\n in sed(1) replacement strings because it is not\n>    portable.\n> - Link to v2: https://patch.msgid.link/20260422-toon-fix-almalinux8-v2-1-45d8471ed0e9@iotcl.com\n> \n> Changes in v2:\n> - Simplify the changes *a lot* by doing the collapsing unconditionally.\n> - Link to v1: https://patch.msgid.link/20260421-toon-fix-almalinux8-v1-1-aec1d54addde@iotcl.com\n> ---\n>   tools/generate-configlist.sh | 5 ++++-\n>   1 file changed, 4 insertions(+), 1 deletion(-)\n> \n> diff --git a/tools/generate-configlist.sh b/tools/generate-configlist.sh\n> index e28054f9e0..d1d2ba4bb7 100755\n> --- a/tools/generate-configlist.sh\n> +++ b/tools/generate-configlist.sh\n> @@ -42,9 +42,12 @@ if test -n \"$DEPFILE\"\n>   then\n>   \tQUOTED_OUTPUT=\"$(printf '%s\\n' \"$OUTPUT\" | sed 's,[&/\\],\\\\&,g')\"\n>   \t{\n> +\t\tprintf '%s' \"$QUOTED_OUTPUT: \"\n>   \t\tprintf '%s\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n>   \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n> -\t\t\tsed -e 's/[# ]/\\\\&/g' -e \"s/^/$QUOTED_OUTPUT: /\"\n> +\t\t\tsed -e 's/[# ]/\\\\&/g' |\n> +\t\t\ttr '\\n' ' '\n> +\t\tprintf '\\n'\n>   \t\tprintf '%s:\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n>   \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n>   \t\t\tsed -e 's/[# ]/\\\\&/g'\n> \n> ---\n> base-commit: 59ff4886a579f4bc91e976fe18590b9ae02c7a08\n> change-id: 20260421-toon-fix-almalinux8-102de9138294\n> \n> \n\n"},{"id":"543810","messageId":"ag7Q5_qz2MCAcdFp@pks.im","threadId":"65533","inReplyTo":"20260515-toon-fix-almalinux8-v3-1-b545a0647f0f@iotcl.com","subject":"Re: [PATCH v3] generate-configlist: collapse depfile for older Ninja","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-21T09:31:19Z","receivedAt":"2026-05-21T09:31:25Z","isPatch":true,"body":"On Fri, May 15, 2026 at 10:42:26AM +0200, Toon Claes wrote:\n> diff --git a/tools/generate-configlist.sh b/tools/generate-configlist.sh\n> index e28054f9e0..d1d2ba4bb7 100755\n> --- a/tools/generate-configlist.sh\n> +++ b/tools/generate-configlist.sh\n> @@ -42,9 +42,12 @@ if test -n \"$DEPFILE\"\n>  then\n>  \tQUOTED_OUTPUT=\"$(printf '%s\\n' \"$OUTPUT\" | sed 's,[&/\\],\\\\&,g')\"\n>  \t{\n> +\t\tprintf '%s' \"$QUOTED_OUTPUT: \"\n>  \t\tprintf '%s\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n>  \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n> -\t\t\tsed -e 's/[# ]/\\\\&/g' -e \"s/^/$QUOTED_OUTPUT: /\"\n> +\t\t\tsed -e 's/[# ]/\\\\&/g' |\n\nThe `-e` switch is now arguably not necessary anymore, but that's not a\nhuge concern.\n\n> +\t\t\ttr '\\n' ' '\n> +\t\tprintf '\\n'\n>  \t\tprintf '%s:\\n' \"$SOURCE_DIR\"/Documentation/*config.adoc \\\n>  \t\t\t\"$SOURCE_DIR\"/Documentation/config/*.adoc |\n>  \t\t\tsed -e 's/[# ]/\\\\&/g'\n\nThe extra printf could've been rolled into the second printf call via\n`printf '\\n%s:\\n'`, but that's not a huge concern, either.\n\nOther than that this looks good to me, thanks!\n\nPatrick\n"}]}