On Windows, the spaces in directory names cause trouble: Michele Locati
reports errors like

exec dotnet 'C:\Program Files\dotnet\sdk\6.0.300\Roslyn\bincore\csc.dll' 
-nologo -optimize+ -debug+ -target:library -out:GNU.Gettext.dll 
'-lib:C:\\Program\' 'Files\\dotnet\\shared\\Microsoft.NETCore.App\\6.0.5' ...
...
warning CS1668: Invalid search path 'C:\\Program\' specified in '/LIB option' 
-- 'directory does not exist'
error CS2001: Source file 
'D:\gettext-2024-10-10\build\gettext-runtime\intl-csharp\Files\\dotnet\\shared\\Microsoft.NETCore.App\\6.0.5'
 could not be found.
make[4]: *** [Makefile:2364: GNU.Gettext.dll] Error 1

This patch fixes it. Tested with dotnet 8 on Windows.

Passing arguments with spaces, as part of an argument list with an arbitrary
number of arguments, to subprocesses is one of the hardest things in shell
scripts. The simpler cases are simple:
  - If there are only a limited number of arguments, just using a different
    variable for each, and avoiding word-expansion through use of double-quotes
    is sufficient.
  - If all arguments of the shell script need to be passed to a subprocess,
    possibly with some being filtered out, it can be done with a single
    loop and the use of 'set -- ...' or 'set x ...'. Examples:
      build-aux/compile (from Automake)
      build-aux/ar-lib (from Automake)
      build-aux/macos-compile
But the general case (that involves reordering the arguments or adding other
arguments) is hard. Stackoverflow [1] says that word-expansion must be
avoided, because it does not support the spaces in arguments and is dangerous.
But it doesn't give a solution, cf. [1][2].
Fortunately, the 'x-to-1.in' script that we already have in Gnulib shows how
it can be done: With 'eval'.

[1] https://stackoverflow.com/questions/3811345/
[2] https://stackoverflow.com/questions/4824590/


2024-10-10  Bruno Haible  <br...@clisp.org>

        csharpcomp-script: Handle directories with spaces correctly.
        Reported by Michele Locati <mich...@locati.it>.
        * build-aux/csharpcomp.sh.in (command_for_print, command_for_eval,
        options_csc_for_print, options_csc_for_eval, sources_csc_for_print,
        sources_csc_for_eval): New variables.
        (sed_protect_1, sed_protect_2a, sed_protect_2b, sed_protect_2c,
        sed_protect_3a, sed_protect_3b): New variables, copied from
        build-aux/x-to-1.in.
        (func_add_word_to_command): New function, copied from
        build-aux/x-to-1.in.
        (func_add_word_to_options_csc, func_add_word_to_sources_csc): New
        functions.
        (options_csc, sources_csc): Remove variables. Use
        func_add_word_to_options_csc, func_add_word_to_sources_csc instead of
        augmenting them.
        Use options_csc_for_print, options_csc_for_eval, sources_csc_for_print,
        sources_csc_for_eval when invoking csc.
        * build-aux/csharpexec.sh.in (sed_quote_subst): Remove unused variable.

2024-10-10  Bruno Haible  <br...@clisp.org>

        java{comp,exec}-script, csharp{comp,exec}-script: Improve debugging.
        * build-aux/javaexec.sh.in: Send debugging output to stderr, not stdout.
        * build-aux/javacomp.sh.in: Likewise.
        * build-aux/csharpexec.sh.in: Likewise.
        * build-aux/csharpcomp.sh.in: Likewise.

>From cd962ee06c2dfa79a6f77fa2a81244bd9bc7c49a Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Thu, 10 Oct 2024 19:15:06 +0200
Subject: [PATCH 1/2] java{comp,exec}-script, csharp{comp,exec}-script: Improve
 debugging.

* build-aux/javaexec.sh.in: Send debugging output to stderr, not stdout.
* build-aux/javacomp.sh.in: Likewise.
* build-aux/csharpexec.sh.in: Likewise.
* build-aux/csharpcomp.sh.in: Likewise.
---
 ChangeLog                  | 8 ++++++++
 build-aux/csharpcomp.sh.in | 6 +++---
 build-aux/csharpexec.sh.in | 6 +++---
 build-aux/javacomp.sh.in   | 4 ++--
 build-aux/javaexec.sh.in   | 6 +++---
 5 files changed, 19 insertions(+), 11 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 04883497b5..b427c5033c 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2024-10-10  Bruno Haible  <br...@clisp.org>
+
+	java{comp,exec}-script, csharp{comp,exec}-script: Improve debugging.
+	* build-aux/javaexec.sh.in: Send debugging output to stderr, not stdout.
+	* build-aux/javacomp.sh.in: Likewise.
+	* build-aux/csharpexec.sh.in: Likewise.
+	* build-aux/csharpcomp.sh.in: Likewise.
+
 2024-10-09  Bruno Haible  <br...@clisp.org>
 
 	csharpcomp: Avoid error on Windows.
diff --git a/build-aux/csharpcomp.sh.in b/build-aux/csharpcomp.sh.in
index fcfc32cd7f..58b53c6bff 100644
--- a/build-aux/csharpcomp.sh.in
+++ b/build-aux/csharpcomp.sh.in
@@ -162,7 +162,7 @@ if test -n "@HAVE_MCS@"; then
 }'
   func_tmpdir
   trap 'rm -rf "$tmp"' HUP INT QUIT TERM
-  test -z "$CSHARP_VERBOSE" || echo mcs $options_mcs $sources
+  test -z "$CSHARP_VERBOSE" || echo mcs $options_mcs $sources 1>&2
   mcs $options_mcs $sources > "$tmp"/mcs.err
   result=$?
   sed -e "$sed_drop_success_line" < "$tmp"/mcs.err >&2
@@ -192,11 +192,11 @@ else
         csc=`cygpath -w "$csc"`
         ;;
     esac
-    test -z "$CSHARP_VERBOSE" || echo dotnet "$csc" $options_csc $sources_csc
+    test -z "$CSHARP_VERBOSE" || echo dotnet "$csc" $options_csc $sources_csc 1>&2
     exec dotnet "$csc" $options_csc $sources_csc
   else
     if test -n "@HAVE_DOTNET_CSC@" || test -n "@HAVE_CSC@"; then
-      test -z "$CSHARP_VERBOSE" || echo csc $options_csc $sources_csc
+      test -z "$CSHARP_VERBOSE" || echo csc $options_csc $sources_csc 1>&2
       exec csc $options_csc $sources_csc
     else
       echo 'C# compiler not found, try installing mono or dotnet, then reconfigure' 1>&2
diff --git a/build-aux/csharpexec.sh.in b/build-aux/csharpexec.sh.in
index 45c95b4b5f..c2ccd3b645 100644
--- a/build-aux/csharpexec.sh.in
+++ b/build-aux/csharpexec.sh.in
@@ -101,7 +101,7 @@ if test -n "@HAVE_MONO@"; then
     MONO_PATH="$CONF_MONO_PATH"
   fi
   export MONO_PATH
-  test -z "$CSHARP_VERBOSE" || echo mono "$@"
+  test -z "$CSHARP_VERBOSE" || echo mono "$@" 1>&2
   exec mono "$@"
 else
   if test -n "@HAVE_DOTNET@"; then
@@ -180,7 +180,7 @@ else
           runtimeconfig_arg=`cygpath -w "$runtimeconfig"`
           ;;
       esac
-      test -z "$CSHARP_VERBOSE" || echo dotnet exec --runtimeconfig "$runtimeconfig_arg" "$prog_arg" "$@"
+      test -z "$CSHARP_VERBOSE" || echo dotnet exec --runtimeconfig "$runtimeconfig_arg" "$prog_arg" "$@" 1>&2
       dotnet exec --runtimeconfig "$runtimeconfig_arg" "$prog_arg" "$@"
       result=$?
       rm -f "$runtimeconfig"
@@ -204,7 +204,7 @@ else
           prog=`cygpath -w "$prog"`
           ;;
       esac
-      test -z "$CSHARP_VERBOSE" || echo clix "$prog" "$@"
+      test -z "$CSHARP_VERBOSE" || echo clix "$prog" "$@" 1>&2
       exec clix "$prog" "$@"
     else
       echo 'C# virtual machine not found, try installing mono or dotnet, then reconfigure' 1>&2
diff --git a/build-aux/javacomp.sh.in b/build-aux/javacomp.sh.in
index 65c4008fe7..a24fa1d410 100644
--- a/build-aux/javacomp.sh.in
+++ b/build-aux/javacomp.sh.in
@@ -37,7 +37,7 @@ if test -n "@HAVE_JAVAC_ENVVAR@"; then
     CLASSPATH="$CONF_CLASSPATH"
   fi
   export CLASSPATH
-  test -z "$JAVA_VERBOSE" || echo "$CONF_JAVAC $@"
+  test -z "$JAVA_VERBOSE" || echo "$CONF_JAVAC $@" 1>&2
   exec $CONF_JAVAC "$@"
 else
   unset JAVA_HOME
@@ -45,7 +45,7 @@ else
     # In this case, $CONF_JAVAC starts with "javac".
     CLASSPATH="$CLASSPATH"
     export CLASSPATH
-    test -z "$JAVA_VERBOSE" || echo "$CONF_JAVAC $@"
+    test -z "$JAVA_VERBOSE" || echo "$CONF_JAVAC $@" 1>&2
     exec $CONF_JAVAC "$@"
   else
     echo 'Java compiler not found, try setting $JAVAC, then reconfigure' 1>&2
diff --git a/build-aux/javaexec.sh.in b/build-aux/javaexec.sh.in
index cedb28d75d..01d5a6fadc 100644
--- a/build-aux/javaexec.sh.in
+++ b/build-aux/javaexec.sh.in
@@ -36,19 +36,19 @@ if test -n "@HAVE_JAVA_ENVVAR@"; then
     CLASSPATH="$CONF_CLASSPATH"
   fi
   export CLASSPATH
-  test -z "$JAVA_VERBOSE" || echo "$CONF_JAVA $@"
+  test -z "$JAVA_VERBOSE" || echo "$CONF_JAVA $@" 1>&2
   exec $CONF_JAVA "$@"
 else
   unset JAVA_HOME
   export CLASSPATH
   if test -n "@HAVE_JAVA@"; then
     # In this case, $CONF_JAVA is "java".
-    test -z "$JAVA_VERBOSE" || echo "$CONF_JAVA $@"
+    test -z "$JAVA_VERBOSE" || echo "$CONF_JAVA $@" 1>&2
     exec $CONF_JAVA "$@"
   else
     if test -n "@HAVE_JRE@"; then
       # In this case, $CONF_JAVA is "jre".
-      test -z "$JAVA_VERBOSE" || echo "$CONF_JAVA $@"
+      test -z "$JAVA_VERBOSE" || echo "$CONF_JAVA $@" 1>&2
       exec $CONF_JAVA "$@"
     else
       echo 'Java virtual machine not found, try setting $JAVA, then reconfigure' 1>&2
-- 
2.34.1

>From b8f013422581af67cdfcddd3beaece2c86150579 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Thu, 10 Oct 2024 19:39:36 +0200
Subject: [PATCH 2/2] csharpcomp-script: Handle directories with spaces
 correctly.

Reported by Michele Locati <mich...@locati.it>.

* build-aux/csharpcomp.sh.in (command_for_print, command_for_eval,
options_csc_for_print, options_csc_for_eval, sources_csc_for_print,
sources_csc_for_eval): New variables.
(sed_protect_1, sed_protect_2a, sed_protect_2b, sed_protect_2c,
sed_protect_3a, sed_protect_3b): New variables, copied from
build-aux/x-to-1.in.
(func_add_word_to_command): New function, copied from
build-aux/x-to-1.in.
(func_add_word_to_options_csc, func_add_word_to_sources_csc): New
functions.
(options_csc, sources_csc): Remove variables. Use
func_add_word_to_options_csc, func_add_word_to_sources_csc instead of
augmenting them.
Use options_csc_for_print, options_csc_for_eval, sources_csc_for_print,
sources_csc_for_eval when invoking csc.
* build-aux/csharpexec.sh.in (sed_quote_subst): Remove unused variable.
---
 ChangeLog                  | 21 +++++++++++
 build-aux/csharpcomp.sh.in | 75 +++++++++++++++++++++++++++++---------
 build-aux/csharpexec.sh.in |  1 -
 3 files changed, 79 insertions(+), 18 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index b427c5033c..0b47332948 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,24 @@
+2024-10-10  Bruno Haible  <br...@clisp.org>
+
+	csharpcomp-script: Handle directories with spaces correctly.
+	Reported by Michele Locati <mich...@locati.it>.
+	* build-aux/csharpcomp.sh.in (command_for_print, command_for_eval,
+	options_csc_for_print, options_csc_for_eval, sources_csc_for_print,
+	sources_csc_for_eval): New variables.
+	(sed_protect_1, sed_protect_2a, sed_protect_2b, sed_protect_2c,
+	sed_protect_3a, sed_protect_3b): New variables, copied from
+	build-aux/x-to-1.in.
+	(func_add_word_to_command): New function, copied from
+	build-aux/x-to-1.in.
+	(func_add_word_to_options_csc, func_add_word_to_sources_csc): New
+	functions.
+	(options_csc, sources_csc): Remove variables. Use
+	func_add_word_to_options_csc, func_add_word_to_sources_csc instead of
+	augmenting them.
+	Use options_csc_for_print, options_csc_for_eval, sources_csc_for_print,
+	sources_csc_for_eval when invoking csc.
+	* build-aux/csharpexec.sh.in (sed_quote_subst): Remove unused variable.
+
 2024-10-10  Bruno Haible  <br...@clisp.org>
 
 	java{comp,exec}-script, csharp{comp,exec}-script: Improve debugging.
diff --git a/build-aux/csharpcomp.sh.in b/build-aux/csharpcomp.sh.in
index 58b53c6bff..2dec12af7e 100644
--- a/build-aux/csharpcomp.sh.in
+++ b/build-aux/csharpcomp.sh.in
@@ -64,21 +64,58 @@ func_tmpdir ()
   }
 }
 
+# In order to construct a command that invokes csc, we need 'eval', because
+# some of the arguments may contain spaces.
+command_for_print=
+command_for_eval=
+options_csc_for_print=
+options_csc_for_eval=
+sources_csc_for_print=
+sources_csc_for_eval=
+# Protecting special characters, hiding them from 'eval':
+# Double each backslash.
+sed_protect_1='s/\\/\\\\/g'
+# Escape each dollar, backquote, double-quote.
+sed_protect_2a='s/\$/\\$/g'
+sed_protect_2b='s/`/\\`/g'
+sed_protect_2c='s/"/\\"/g'
+# Add double-quotes at the beginning and end of the word.
+sed_protect_3a='1s/^/"/'
+sed_protect_3b='$s/$/"/'
+func_add_word_to_command ()
+{
+  command_for_print="${command_for_print:+$command_for_print }$1"
+  word_protected=`echo "$1" | sed -e "$sed_protect_1" -e "$sed_protect_2a" -e "$sed_protect_2b" -e "$sed_protect_2c" -e "$sed_protect_3a" -e "$sed_protect_3b"`
+  command_for_eval="${command_for_eval:+$command_for_eval }$word_protected"
+}
+func_add_word_to_options_csc ()
+{
+  options_csc_for_print="${options_csc_for_print:+$options_csc_for_print }$1"
+  word_protected=`echo "$1" | sed -e "$sed_protect_1" -e "$sed_protect_2a" -e "$sed_protect_2b" -e "$sed_protect_2c" -e "$sed_protect_3a" -e "$sed_protect_3b"`
+  options_csc_for_eval="${options_csc_for_eval:+$options_csc_for_eval }$word_protected"
+}
+func_add_word_to_sources_csc ()
+{
+  sources_csc_for_print="${sources_csc_for_print:+$sources_csc_for_print }$1"
+  word_protected=`echo "$1" | sed -e "$sed_protect_1" -e "$sed_protect_2a" -e "$sed_protect_2b" -e "$sed_protect_2c" -e "$sed_protect_3a" -e "$sed_protect_3b"`
+  sources_csc_for_eval="${sources_csc_for_eval:+$sources_csc_for_eval }$word_protected"
+}
+
 sed_quote_subst='s/\([|&;<>()$`"'"'"'*?[#~=% 	\\]\)/\\\1/g'
+
 options_mcs=
-options_csc="-nologo"
 sources=
-sources_csc=
+func_add_word_to_options_csc "-nologo"
 while test $# != 0; do
   case "$1" in
     -o)
       case "$2" in
         *.dll)
           options_mcs="$options_mcs -target:library"
-          options_csc="$options_csc -target:library"
+          func_add_word_to_options_csc "-target:library"
           ;;
         *.exe)
-          options_csc="$options_csc -target:exe"
+          func_add_word_to_options_csc "-target:exe"
           ;;
       esac
       options_mcs="$options_mcs -out:"`echo "$2" | sed -e "$sed_quote_subst"`
@@ -90,7 +127,7 @@ while test $# != 0; do
           arg=`cygpath -w "$arg"`
           ;;
       esac
-      options_csc="$options_csc -out:"`echo "$arg" | sed -e "$sed_quote_subst"`
+      func_add_word_to_options_csc "-out:$arg"
       shift
       ;;
     -L)
@@ -103,20 +140,20 @@ while test $# != 0; do
           arg=`cygpath -w "$arg"`
           ;;
       esac
-      options_csc="$options_csc -lib:"`echo "$arg" | sed -e "$sed_quote_subst"`
+      func_add_word_to_options_csc "-lib:$arg"
       shift
       ;;
     -l)
       options_mcs="$options_mcs -reference:"`echo "$2" | sed -e "$sed_quote_subst"`
-      options_csc="$options_csc -reference:"`echo "$2" | sed -e "$sed_quote_subst"`".dll"
+      func_add_word_to_options_csc "-reference:$2.dll"
       shift
       ;;
     -O)
-      options_csc="$options_csc -optimize+"
+      func_add_word_to_options_csc "-optimize+"
       ;;
     -g)
       options_mcs="$options_mcs -debug"
-      options_csc="$options_csc -debug+"
+      func_add_word_to_options_csc "-debug+"
       ;;
     -*)
       echo "csharpcomp: unknown option '$1'" 1>&2
@@ -132,7 +169,7 @@ while test $# != 0; do
           arg=`cygpath -w "$arg"`
           ;;
       esac
-      options_csc="$options_csc -resource:"`echo "$arg" | sed -e "$sed_quote_subst"`
+      func_add_word_to_options_csc "-resource:$arg"
       ;;
     *.cs)
       sources="$sources "`echo "$1" | sed -e "$sed_quote_subst"`
@@ -144,7 +181,7 @@ while test $# != 0; do
           arg=`cygpath -w "$arg"`
           ;;
       esac
-      sources_csc="$sources_csc "`echo "$arg" | sed -e "$sed_quote_subst"`
+      func_add_word_to_sources_csc "$arg"
       ;;
     *)
       echo "csharpcomp: unknown type of argument '$1'" 1>&2
@@ -179,25 +216,29 @@ else
         arg=`cygpath -w "$arg"`
         ;;
     esac
-    options_csc="$options_csc -lib:"`echo "$arg" | sed -e "$sed_quote_subst"`
+    func_add_word_to_options_csc "-lib:$arg"
     for file in `cd "$dotnet_runtime_dir" && echo [ABCDEFGHIJKLMNOPQRSTUVWXYZ]*.dll`; do
       case "$file" in
         *.Native.*) ;;
-        *) options_csc="$options_csc -reference:"`echo "$file" | sed -e "$sed_quote_subst"` ;;
+        *) func_add_word_to_options_csc "-reference:$file" ;;
       esac
     done
+    func_add_word_to_command dotnet
     csc="$dotnet_sdk_dir/Roslyn/bincore/csc.dll"
     case "@build_os@" in
       cygwin*)
         csc=`cygpath -w "$csc"`
         ;;
     esac
-    test -z "$CSHARP_VERBOSE" || echo dotnet "$csc" $options_csc $sources_csc 1>&2
-    exec dotnet "$csc" $options_csc $sources_csc
+    func_add_word_to_command "$csc"
+    test -z "$CSHARP_VERBOSE" || echo "$command_for_print $options_csc_for_print $sources_csc_for_print" 1>&2
+    eval "$command_for_eval $options_csc_for_eval $sources_csc_for_eval"
+    exit $?
   else
     if test -n "@HAVE_DOTNET_CSC@" || test -n "@HAVE_CSC@"; then
-      test -z "$CSHARP_VERBOSE" || echo csc $options_csc $sources_csc 1>&2
-      exec csc $options_csc $sources_csc
+      test -z "$CSHARP_VERBOSE" || echo "csc $options_csc_for_print $sources_csc_for_print" 1>&2
+      eval "csc $options_csc_for_eval $sources_csc_for_eval"
+      exit $?
     else
       echo 'C# compiler not found, try installing mono or dotnet, then reconfigure' 1>&2
       exit 1
diff --git a/build-aux/csharpexec.sh.in b/build-aux/csharpexec.sh.in
index c2ccd3b645..a7a5a277c0 100644
--- a/build-aux/csharpexec.sh.in
+++ b/build-aux/csharpexec.sh.in
@@ -59,7 +59,6 @@ func_tmpdir ()
   }
 }
 
-sed_quote_subst='s/\([|&;<>()$`"'"'"'*?[#~=% 	\\]\)/\\\1/g'
 libdirs_mono=
 libdirs_dotnet=
 prog=
-- 
2.34.1

Reply via email to