> On 21 Aug 2026, at 12:50, <[email protected]> <[email protected]> wrote:
> 
> From: Kyrylo Tkachov <[email protected]>
> 
> This is an attempt to fix bootstrap comparison on Darwin and Solaris.
> Rainer, Iain, could you help with testing it?

Sure - I will include it in a run over the weekend.
Iain

> 
> The comparison writes a makefile and runs it under $(MAKE).  Building and
> running that makefile assumed GNU sed, and assumed more of the shell than
> several hosts provide.  Four defects, all from 54c3bdc8ad6.
> 
> The comparison command was escaped for the generated makefile with
> 
>  printf '%s' "$cmp_raw" | sed 's,\$,$$,g'
> 
> whose input carries no trailing newline.  GNU sed passes that through
> unchanged, a POSIX sed terminates the last line, and Solaris /usr/bin/sed
> drops it.  The first splits the recipe in two, and the sub-make stops with
> 
>  compare.42895.mk:5: *** missing separator.  Stop.
> 
> The second leaves a bare redirection where the comparison should be, so every
> object is judged equal and the bootstrap reports "Comparison successful"
> having compared nothing.  Double the '$' characters with $(subst), which make
> can do on its own.
> 
> The exit traps use "trap - 0", which Solaris 10 /bin/sh takes for a command
> named '-', and recover the status with "$?", which Zsh and Solaris 10 /bin/sh
> answer with the status of the command before the "exit" rather than its
> argument.  The recipe reports a comparison failure with "exit 1" right after
> a successful "mv", so on those shells the trap recovers zero and lets the
> failure through as success.  A trap that only removes files needs neither.
> Let the shell exit with the status it already has.
> 
> The comparator fallback installs its trap in a subshell that ends in "cmp",
> and several shells do not run an exit trap from a subshell whose last command
> is not a builtin.  Now that the temporaries are process specific, a trap that
> does not run leaks a pair of files per object.  End the subshell with a
> builtin.
> 
> The generated makefile did not name the shell.  make neither exports SHELL
> nor lets a sub-make inherit it, so the comparisons ran under /bin/sh rather
> than the shell configure chose.
> 
> The serial comparison removed .bad_compare before starting.  That file is now
> written only by a comparison that completes and finds a difference, so a
> sub-make that fails first leaves the previous run's result in place, still
> named in the failure message.  Shards from a run killed with SIGKILL survive
> too, keyed on a pid a later comparison can draw.  Remove both up front.
> 
> Ok for trunk?
> Thanks,
> Kyrill
> 
> ChangeLog:
> 
> PR bootstrap/126875
> * Makefile.tpl ([+compare-target+]): Escape the comparison command
> with $(subst) rather than a sed pipe.  Clean up from the exit trap
> without resetting it or re-deriving the exit status.  Name the shell
> in the generated makefile.  Remove any previous result file and
> result shards before comparing.
> * Makefile.in: Regenerate.
> * configure: Regenerate.
> 
> config/ChangeLog:
> 
> PR other/126875
> * acx.m4 (ACX_PROG_CMP_IGNORE_INITIAL): Clean up from the exit trap
> without resetting it, and end the fallback subshell with a builtin so
> that the trap runs.
> 
> Signed-off-by: Kyrylo Tkachov <[email protected]>
> ---
> Makefile.in   | 30 ++++++++++++++++++++----------
> Makefile.tpl  | 15 ++++++++++-----
> config/acx.m4 |  7 +++----
> configure     |  7 +++----
> 4 files changed, 36 insertions(+), 23 deletions(-)
> 
> diff --git a/Makefile.in b/Makefile.in
> index 5cca69c3f52..24374257381 100644
> --- a/Makefile.in
> +++ b/Makefile.in
> @@ -66024,7 +66024,10 @@ do-clean: clean-stage3
> 
> # Run the comparisons in parallel through a generated sub-makefile under
> # the jobserver.  Each failing recipe writes a separate result shard that
> -# is collected after the sub-make finishes.
> +# is collected after the sub-make finishes.  The comparison command is
> +# written into that makefile, so double its '$' characters to survive the
> +# expansion the sub-make performs when it reads them back, and name the
> +# shell there, which a sub-make does not inherit.
> compare:
> @r=`${PWD_COMMAND}`; export r; \
> s=`cd $(srcdir); ${PWD_COMMAND}`; export s; \
> @@ -66036,22 +66039,24 @@ compare:
> compare_id=$$$$; \
> bad_compare=.bad_compare.$$compare_id; export bad_compare; \
> compare_makefile=compare.$$compare_id.mk; \
> - trap 'st=$$?; rm -f "$$compare_makefile" "$$bad_compare" \
> -  "$$bad_compare".*; trap - 0; exit $$st' 0; \
> + trap ':; rm -f "$$compare_makefile" "$$bad_compare" \
> +  "$$bad_compare".*' 0; \
> trap 'exit 1' 1 2 3 15; \
> + rm -f .bad_compare "$$bad_compare" "$$bad_compare".*; \
> echo Comparing stages 2 and 3; \
>         sed=`echo stage3 | sed 's,^stage,,;s,.,.,g'`; \
> files=`find stage3-* -name "*$(objext)" -print | \
> sed -n s,^stage$$sed-,,p`; \
> - cmp_raw='$(do-compare)'; \
> + cmp_cmd='$(subst $$,$$$$,$(do-compare))'; \
> { \
> +  echo 'SHELL = $(SHELL)'; \
>  echo 'all:'; \
>  echo '.PHONY: all FORCE'; \
>  echo 'FORCE:'; \
>  printf 'compare/%%: FORCE ; @'; \
>  printf 'f1=$$$$r/stage2-$$*; '; \
>  printf 'f2=$$$$r/stage3-$$*; '; \
> -  printf '%s' "$$cmp_raw" | sed 's,\$$,$$$$,g'; \
> +  printf '%s' "$$cmp_cmd"; \
>  printf ' > /dev/null 2>&1; st=$$$$?; '; \
>  printf 'if test $$$$st -eq 1; then '; \
>  printf 'case $$* in '; \
> @@ -66484,7 +66489,10 @@ do-clean: clean-stage4
> 
> # Run the comparisons in parallel through a generated sub-makefile under
> # the jobserver.  Each failing recipe writes a separate result shard that
> -# is collected after the sub-make finishes.
> +# is collected after the sub-make finishes.  The comparison command is
> +# written into that makefile, so double its '$' characters to survive the
> +# expansion the sub-make performs when it reads them back, and name the
> +# shell there, which a sub-make does not inherit.
> compare3:
> @r=`${PWD_COMMAND}`; export r; \
> s=`cd $(srcdir); ${PWD_COMMAND}`; export s; \
> @@ -66496,22 +66504,24 @@ compare3:
> compare_id=$$$$; \
> bad_compare=.bad_compare.$$compare_id; export bad_compare; \
> compare_makefile=compare3.$$compare_id.mk; \
> - trap 'st=$$?; rm -f "$$compare_makefile" "$$bad_compare" \
> -  "$$bad_compare".*; trap - 0; exit $$st' 0; \
> + trap ':; rm -f "$$compare_makefile" "$$bad_compare" \
> +  "$$bad_compare".*' 0; \
> trap 'exit 1' 1 2 3 15; \
> + rm -f .bad_compare "$$bad_compare" "$$bad_compare".*; \
> echo Comparing stages 3 and 4; \
>         sed=`echo stage4 | sed 's,^stage,,;s,.,.,g'`; \
> files=`find stage4-* -name "*$(objext)" -print | \
> sed -n s,^stage$$sed-,,p`; \
> - cmp_raw='$(do-compare3)'; \
> + cmp_cmd='$(subst $$,$$$$,$(do-compare3))'; \
> { \
> +  echo 'SHELL = $(SHELL)'; \
>  echo 'all:'; \
>  echo '.PHONY: all FORCE'; \
>  echo 'FORCE:'; \
>  printf 'compare3/%%: FORCE ; @'; \
>  printf 'f1=$$$$r/stage3-$$*; '; \
>  printf 'f2=$$$$r/stage4-$$*; '; \
> -  printf '%s' "$$cmp_raw" | sed 's,\$$,$$$$,g'; \
> +  printf '%s' "$$cmp_cmd"; \
>  printf ' > /dev/null 2>&1; st=$$$$?; '; \
>  printf 'if test $$$$st -eq 1; then '; \
>  printf 'case $$* in '; \
> diff --git a/Makefile.tpl b/Makefile.tpl
> index 98bd03e9a33..3123c21eab3 100644
> --- a/Makefile.tpl
> +++ b/Makefile.tpl
> @@ -1823,7 +1823,10 @@ do-clean: clean-stage[+id+]
> [+ IF compare-target +]
> # Run the comparisons in parallel through a generated sub-makefile under
> # the jobserver.  Each failing recipe writes a separate result shard that
> -# is collected after the sub-make finishes.
> +# is collected after the sub-make finishes.  The comparison command is
> +# written into that makefile, so double its '$' characters to survive the
> +# expansion the sub-make performs when it reads them back, and name the
> +# shell there, which a sub-make does not inherit.
> [+compare-target+]:
> @r=`${PWD_COMMAND}`; export r; \
> s=`cd $(srcdir); ${PWD_COMMAND}`; export s; \
> @@ -1835,22 +1838,24 @@ do-clean: clean-stage[+id+]
> compare_id=$$$$; \
> bad_compare=.bad_compare.$$compare_id; export bad_compare; \
> compare_makefile=[+compare-target+].$$compare_id.mk; \
> - trap 'st=$$?; rm -f "$$compare_makefile" "$$bad_compare" \
> -  "$$bad_compare".*; trap - 0; exit $$st' 0; \
> + trap ':; rm -f "$$compare_makefile" "$$bad_compare" \
> +  "$$bad_compare".*' 0; \
> trap 'exit 1' 1 2 3 15; \
> + rm -f .bad_compare "$$bad_compare" "$$bad_compare".*; \
> echo Comparing stages [+prev+] and [+id+]; \
>         sed=`echo stage[+id+] | sed 's,^stage,,;s,.,.,g'`; \
> files=`find stage[+id+]-* -name "*$(objext)" -print | \
> sed -n s,^stage$$sed-,,p`; \
> - cmp_raw='$(do-[+compare-target+])'; \
> + cmp_cmd='$(subst $$,$$$$,$(do-[+compare-target+]))'; \
> { \
> +  echo 'SHELL = $(SHELL)'; \
>  echo 'all:'; \
>  echo '.PHONY: all FORCE'; \
>  echo 'FORCE:'; \
>  printf '[+compare-target+]/%%: FORCE ; @'; \
>  printf 'f1=$$$$r/stage[+prev+]-$$*; '; \
>  printf 'f2=$$$$r/stage[+id+]-$$*; '; \
> -  printf '%s' "$$cmp_raw" | sed 's,\$$,$$$$,g'; \
> +  printf '%s' "$$cmp_cmd"; \
>  printf ' > /dev/null 2>&1; st=$$$$?; '; \
>  printf 'if test $$$$st -eq 1; then '; \
>  printf 'case $$* in '; \
> diff --git a/config/acx.m4 b/config/acx.m4
> index 5547cbeb9b8..0af0b371bf2 100644
> --- a/config/acx.m4
> +++ b/config/acx.m4
> @@ -477,16 +477,15 @@ AC_DEFUN([ACX_PROG_CMP_IGNORE_INITIAL],
> [AC_CACHE_CHECK([how to compare bootstrapped objects], gcc_cv_prog_cmp_skip,
> [ echo abfoo >t1
>   echo cdfoo >t2
> -  gcc_cv_prog_cmp_skip='(trap "st=\$$?; rm -f tmp-foo1.$$$$ '
> -  gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'tmp-foo2.$$$$; '
> -  gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'trap - 0; exit \$$st" 0; '
> +  gcc_cv_prog_cmp_skip='(trap ":; rm -f tmp-foo1.$$$$ '
> +  gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'tmp-foo2.$$$$" 0; '
>   gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'trap "exit 2" 1 2 3 15; '
>   gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'tail -c +17 $$f1 '
>   gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'> tmp-foo1.$$$$ || exit 2; '
>   gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'tail -c +17 $$f2 '
>   gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'> tmp-foo2.$$$$ || exit 2; '
>   gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'cmp tmp-foo1.$$$$ '
> -  gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'tmp-foo2.$$$$)'
> +  gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'tmp-foo2.$$$$; exit $$?)'
>   if cmp t1 t2 2 2 > /dev/null 2>&1; then
>     if cmp t1 t2 1 1 > /dev/null 2>&1; then
>       :
> diff --git a/configure b/configure
> index 4db0edbdccd..fc27b526f78 100755
> --- a/configure
> +++ b/configure
> @@ -6123,16 +6123,15 @@ if ${gcc_cv_prog_cmp_skip+:} false; then :
> else
>    echo abfoo >t1
>   echo cdfoo >t2
> -  gcc_cv_prog_cmp_skip='(trap "st=\$$?; rm -f tmp-foo1.$$$$ '
> -  gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'tmp-foo2.$$$$; '
> -  gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'trap - 0; exit \$$st" 0; '
> +  gcc_cv_prog_cmp_skip='(trap ":; rm -f tmp-foo1.$$$$ '
> +  gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'tmp-foo2.$$$$" 0; '
>   gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'trap "exit 2" 1 2 3 15; '
>   gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'tail -c +17 $$f1 '
>   gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'> tmp-foo1.$$$$ || exit 2; '
>   gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'tail -c +17 $$f2 '
>   gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'> tmp-foo2.$$$$ || exit 2; '
>   gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'cmp tmp-foo1.$$$$ '
> -  gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'tmp-foo2.$$$$)'
> +  gcc_cv_prog_cmp_skip="$gcc_cv_prog_cmp_skip"'tmp-foo2.$$$$; exit $$?)'
>   if cmp t1 t2 2 2 > /dev/null 2>&1; then
>     if cmp t1 t2 1 1 > /dev/null 2>&1; then
>       :
> -- 
> 2.50.1 (Apple Git-155)
> 

Reply via email to