The csharpexec, csharpcomp modules allow to compile and run C# programs.
So far, they support
  - Mono,
  - the (20 years old, proprietary) SSCLI implementation from Microsoft.

Nowadays, Mono is in minimal maintenance, and the common C# implementation
is .NET (from Microsoft, under MIT license). This implementation can be
obtained
  - either from source,
  - or as Linux binaries from https://versionsof.net/,
  - or as binary packages for some distros such as Ubuntu, CentOS
    (cf. https://repology.org/project/dotnet/versions ).

This series of patches adds support for this implementation to the
csharpexec, csharpcomp modules.

Tested with
  - dotnet 6, 7, 8 on Ubuntu,
  - MSVC (dotnet 6) on Windows and Cygwin.


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

        csharpcomp: Add support for dotnet.
        * lib/csharpcomp.c: Include <dirent.h>, concat-filename.h, xvasprintf.h.
        (name_is_dll): New function, from lib/csharpexec.c.
        (compile_csharp_using_dotnet): New function.
        (compile_csharp_class): Invoke compile_csharp_using_dotnet.
        * modules/csharpcomp (Depends-on): Add xconcat-filename, scandir,
        alphasort, xvasprintf.

        csharpcomp-script: Add support for dotnet.
        * m4/csharpcomp.m4 (gt_CSHARPCOMP): Support 'dotnet' as implementation.
        Set HAVE_DOTNET_SDK, HAVE_DOTNET_CSC.
        * build-aux/csharpcomp.sh.in: Add implementations for the cases
        $HAVE_DOTNET_SDK = 1 and $HAVE_DOTNET_CSC = 1.

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

        csharpexec: Add support for dotnet.
        * lib/csharpexec.c: Include <dirent.h>, <errno.h>, <sys/stat.h>,
        dirname.h, concat-filename.h, canonicalize.h, spawn-pipe.h,
        wait-process.h, xalloc.h, copy-file.h, clean-temp-simple.h,
        clean-temp.h.
        (name_is_dll, execute_csharp_using_dotnet): New functions.
        (execute_csharp_program): Invoke execute_csharp_using_dotnet.
        * modules/csharpexec (Depends-on): Add stat, dirname, xconcat-filename,
        canonicalize, spawn-pipe, wait-process, scandir, alphasort, copy-file,
        clean-temp-simple, clean-temp.

        csharpexec-script: Add support for dotnet.
        * m4/csharpexec.m4 (gt_CSHARPEXEC): Support 'dotnet' as implementation.
        Set HAVE_DOTNET.
        * build-aux/csharpexec.sh.in (func_tmpdir): New function, copied from
        build-aux/csharpcomp.sh.in.
        Add implementation for the case $HAVE_DOTNET = 1.

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

        csharpexec-script, csharpcomp-script: Prepare support for dotnet.
        * m4/csharp.m4 (gt_CSHARP_CHOICE): Recognize 'dotnet' as value of
        --enable-csharp.

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

        csharpcomp: Behave like csharpcomp-script.
        * lib/csharpcomp.c (compile_csharp_using_sscli): Pass the option
        '-nologo' to csc.

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

        csharpcomp: Fix memory management bug (regression yesterday).
        * lib/csharpcomp.c (compile_csharp_using_sscli): Allocate the source
        options with malloc() always, not sometimes with malloca() and sometimes
        with malloc().

>From 5949b7855aff36e7e084d7f2c15b62575b258171 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 9 Oct 2024 03:05:33 +0200
Subject: [PATCH 1/7] csharpcomp: Fix memory management bug (regression
 yesterday).

* lib/csharpcomp.c (compile_csharp_using_sscli): Allocate the source
options with malloc() always, not sometimes with malloca() and sometimes
with malloc().
---
 ChangeLog        | 7 +++++++
 lib/csharpcomp.c | 9 +++------
 2 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 27e1dca18c..1ddf1e11b6 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2024-10-08  Bruno Haible  <br...@clisp.org>
+
+	csharpcomp: Fix memory management bug (regression yesterday).
+	* lib/csharpcomp.c (compile_csharp_using_sscli): Allocate the source
+	options with malloc() always, not sometimes with malloca() and sometimes
+	with malloc().
+
 2024-10-07  Bruno Haible  <br...@clisp.org>
 
 	csharpcomp: Improve Cygwin support.
diff --git a/lib/csharpcomp.c b/lib/csharpcomp.c
index a0139c5b85..71e9633355 100644
--- a/lib/csharpcomp.c
+++ b/lib/csharpcomp.c
@@ -324,7 +324,7 @@ compile_csharp_using_sscli (const char * const *sources,
          we need to use cygpath_w.  */
       malloced =
         (char **)
-        xmalloca ((1 + libdirs_count + sources_count) * sizeof (char *));
+        xmalloca ((1 + libdirs_count + sources_count * 2) * sizeof (char *));
       mallocedp = malloced;
 
       argc =
@@ -377,10 +377,10 @@ compile_csharp_using_sscli (const char * const *sources,
                          10) == 0)
             {
               char *option =
-                (char *) xmalloca (10 + strlen (source_file_converted) + 1);
-
+                (char *) xmalloc (10 + strlen (source_file_converted) + 1);
               memcpy (option, "-resource:", 10);
               strcpy (option + 10, source_file_converted);
+              *mallocedp++ = option;
               *argp++ = option;
             }
           else
@@ -404,9 +404,6 @@ compile_csharp_using_sscli (const char * const *sources,
 
       for (i = 2; i < 3 + libdirs_count + libraries_count; i++)
         freea ((char *) argv[i]);
-      for (i = 0; i < sources_count; i++)
-        if (argv[argc - sources_count + i] != sources[i])
-          freea ((char *) argv[argc - sources_count + i]);
       while (mallocedp > malloced)
         free (*--mallocedp);
       freea (argv);
-- 
2.34.1

>From 5b3286f6bb69abfbeebc693aa75b08205ed739ac Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 9 Oct 2024 03:09:15 +0200
Subject: [PATCH 2/7] csharpcomp: Behave like csharpcomp-script.

* lib/csharpcomp.c (compile_csharp_using_sscli): Pass the option
'-nologo' to csc.
---
 ChangeLog        | 6 ++++++
 lib/csharpcomp.c | 5 +++--
 2 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 1ddf1e11b6..20c091f007 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2024-10-08  Bruno Haible  <br...@clisp.org>
+
+	csharpcomp: Behave like csharpcomp-script.
+	* lib/csharpcomp.c (compile_csharp_using_sscli): Pass the option
+	'-nologo' to csc.
+
 2024-10-08  Bruno Haible  <br...@clisp.org>
 
 	csharpcomp: Fix memory management bug (regression yesterday).
diff --git a/lib/csharpcomp.c b/lib/csharpcomp.c
index 71e9633355..28247ed1a7 100644
--- a/lib/csharpcomp.c
+++ b/lib/csharpcomp.c
@@ -328,12 +328,13 @@ compile_csharp_using_sscli (const char * const *sources,
       mallocedp = malloced;
 
       argc =
-        1 + 1 + 1 + libdirs_count + libraries_count
+        2 + 1 + 1 + libdirs_count + libraries_count
         + (optimize ? 1 : 0) + (debug ? 1 : 0) + sources_count;
       argv = (const char **) xmalloca ((argc + 1) * sizeof (const char *));
 
       argp = argv;
       *argp++ = "csc";
+      *argp++ = "-nologo";
       *argp++ = (output_is_library ? "-target:library" : "-target:exe");
       {
         char *output_file_converted = cygpath_w (output_file);
@@ -402,7 +403,7 @@ compile_csharp_using_sscli (const char * const *sources,
                             false, false, false, false,
                             true, true, NULL);
 
-      for (i = 2; i < 3 + libdirs_count + libraries_count; i++)
+      for (i = 3; i < 4 + libdirs_count + libraries_count; i++)
         freea ((char *) argv[i]);
       while (mallocedp > malloced)
         free (*--mallocedp);
-- 
2.34.1

>From 9949a97e8b3962b49354770f85c4e18a67706572 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 9 Oct 2024 03:11:32 +0200
Subject: [PATCH 3/7] csharpexec-script, csharpcomp-script: Prepare support for
 dotnet.

* m4/csharp.m4 (gt_CSHARP_CHOICE): Recognize 'dotnet' as value of
--enable-csharp.
---
 ChangeLog    |  6 ++++++
 m4/csharp.m4 | 14 +++++++++++---
 2 files changed, 17 insertions(+), 3 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 20c091f007..af1b11f8d9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2024-10-08  Bruno Haible  <br...@clisp.org>
+
+	csharpexec-script, csharpcomp-script: Prepare support for dotnet.
+	* m4/csharp.m4 (gt_CSHARP_CHOICE): Recognize 'dotnet' as value of
+	--enable-csharp.
+
 2024-10-08  Bruno Haible  <br...@clisp.org>
 
 	csharpcomp: Behave like csharpcomp-script.
diff --git a/m4/csharp.m4 b/m4/csharp.m4
index 06a71c5f32..8b30a4ad4d 100644
--- a/m4/csharp.m4
+++ b/m4/csharp.m4
@@ -1,17 +1,21 @@
 # csharp.m4
-# serial 4
+# serial 5
 dnl Copyright (C) 2004-2005, 2009-2024 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
 dnl with or without modifications, as long as this notice is preserved.
 
 # Sets CSHARP_CHOICE to the preferred C# implementation:
-# 'mono' or 'any' or 'no'.
+# 'mono' or 'dotnet' or 'any' or 'no'.
+# Here
+#   - 'mono' means <https://en.wikipedia.org/wiki/Mono_(software)>.
+#   - 'dotnet' means the (newer) .NET <https://en.wikipedia.org/wiki/.NET>,
+#     *not* the .NET framework <https://en.wikipedia.org/wiki/.NET_Framework>.
 AC_DEFUN([gt_CSHARP_CHOICE],
 [
   AC_MSG_CHECKING([for preferred C[#] implementation])
   AC_ARG_ENABLE([csharp],
-    [  --enable-csharp[[=IMPL]]  choose preferred C[#] implementation (mono)],
+    [  --enable-csharp[[=IMPL]]  choose preferred C[#] implementation (mono, dotnet)],
     [CSHARP_CHOICE="$enableval"],
     CSHARP_CHOICE=any)
   AC_SUBST([CSHARP_CHOICE])
@@ -21,5 +25,9 @@ AC_DEFUN([gt_CSHARP_CHOICE]
       AC_DEFINE([CSHARP_CHOICE_MONO], [1],
         [Define if mono is the preferred C# implementation.])
       ;;
+    dotnet)
+      AC_DEFINE([CSHARP_CHOICE_DOTNET], [1],
+        [Define if dotnet is the preferred C# implementation.])
+      ;;
   esac
 ])
-- 
2.34.1

>From d81eb1e88b7493d35d85c2f9fc17c68a8e9b50f9 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 9 Oct 2024 03:12:42 +0200
Subject: [PATCH 4/7] csharpexec-script: Add support for dotnet.

* m4/csharpexec.m4 (gt_CSHARPEXEC): Support 'dotnet' as implementation.
Set HAVE_DOTNET.
* build-aux/csharpexec.sh.in (func_tmpdir): New function, copied from
build-aux/csharpcomp.sh.in.
Add implementation for the case $HAVE_DOTNET = 1.
---
 ChangeLog                  |   9 +++
 build-aux/csharpexec.sh.in | 148 +++++++++++++++++++++++++++++++++----
 m4/csharpexec.m4           |  16 +++-
 3 files changed, 155 insertions(+), 18 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index af1b11f8d9..441bdbb7fb 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2024-10-08  Bruno Haible  <br...@clisp.org>
+
+	csharpexec-script: Add support for dotnet.
+	* m4/csharpexec.m4 (gt_CSHARPEXEC): Support 'dotnet' as implementation.
+	Set HAVE_DOTNET.
+	* build-aux/csharpexec.sh.in (func_tmpdir): New function, copied from
+	build-aux/csharpcomp.sh.in.
+	Add implementation for the case $HAVE_DOTNET = 1.
+
 2024-10-08  Bruno Haible  <br...@clisp.org>
 
 	csharpexec-script, csharpcomp-script: Prepare support for dotnet.
diff --git a/build-aux/csharpexec.sh.in b/build-aux/csharpexec.sh.in
index 83d60ca870..45c95b4b5f 100644
--- a/build-aux/csharpexec.sh.in
+++ b/build-aux/csharpexec.sh.in
@@ -28,13 +28,46 @@
 # Options:
 #   -L DIRECTORY      search for C# libraries also in DIRECTORY
 
+# func_tmpdir
+# creates a temporary directory.
+# Sets variable
+# - tmp             pathname of freshly created temporary directory
+func_tmpdir ()
+{
+  # Use the environment variable TMPDIR, falling back to /tmp. This allows
+  # users to specify a different temporary directory, for example, if their
+  # /tmp is filled up or too small.
+  : "${TMPDIR=/tmp}"
+  {
+    # Use the mktemp program if available. If not available, hide the error
+    # message.
+    tmp=`(umask 077 && mktemp -d -q "$TMPDIR/gtXXXXXX") 2>/dev/null` &&
+    test -n "$tmp" && test -d "$tmp"
+  } ||
+  {
+    # Use a simple mkdir command. It is guaranteed to fail if the directory
+    # already exists.  $RANDOM is bash specific and expands to empty in shells
+    # other than bash, ksh and zsh.  Its use does not increase security;
+    # rather, it minimizes the probability of failure in a very cluttered /tmp
+    # directory.
+    tmp=$TMPDIR/gt$$-$RANDOM
+    (umask 077 && mkdir "$tmp")
+  } ||
+  {
+    echo "$0: cannot create a temporary directory in $TMPDIR" >&2
+    { (exit 1); exit 1; }
+  }
+}
+
 sed_quote_subst='s/\([|&;<>()$`"'"'"'*?[#~=% 	\\]\)/\\\1/g'
 libdirs_mono=
+libdirs_dotnet=
 prog=
 while test $# != 0; do
   case "$1" in
     -L)
       libdirs_mono="${libdirs_mono:+$libdirs_mono@MONO_PATH_SEPARATOR@}$2"
+      libdirs_dotnet="${libdirs_dotnet:+$libdirs_dotnet|}$2"
       shift
       ;;
     -*)
@@ -71,26 +104,111 @@ if test -n "@HAVE_MONO@"; then
   test -z "$CSHARP_VERBOSE" || echo mono "$@"
   exec mono "$@"
 else
-  if test -n "@HAVE_CLIX@"; then
-    CONF_CLIX_PATH='@CLIX_PATH@'
-    if test -n "$libdirs_mono"; then
-      @CLIX_PATH_VAR@="$libdirs_mono${CONF_CLIX_PATH:+@MONO_PATH_SEPARATOR@$CONF_CLIX_PATH}"
-    else
-      @CLIX_PATH_VAR@="$CONF_CLIX_PATH"
-    fi
-    export @CLIX_PATH_VAR@
+  if test -n "@HAVE_DOTNET@"; then
+    # Invoke 'dotnet $prog ...'.
+    # Documentation:
+    # <https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet#options-for-running-an-application>
+    # This could be optimized on Windows platforms, because C# .exe files are
+    # directly executable there. But there's no point in optimizing specifically
+    # a non-free platform, especially since it would increase the test matrix.
     shift
-    # On Windows, assume that 'clix' is a native Windows program,
-    # not a Cygwin program.
+    # On Windows, assume that 'dotnet' is a native Windows program, not a Cygwin program.
+    prog_arg="$prog"
     case "@build_os@" in
       cygwin*)
-        prog=`cygpath -w "$prog"`
+        prog_arg=`cygpath -w "$prog"`
         ;;
     esac
-    test -z "$CSHARP_VERBOSE" || echo clix "$prog" "$@"
-    exec clix "$prog" "$@"
+    # Handle the -L options.
+    # The way this works is that we have to copy (or symlink) the DLLs into the
+    # directory where FOO.exe resides.
+    # Maybe there is another way to do this, but I haven't found it, trying
+    #   - the --additionalprobingpath command-line option,
+    #   - the additionalProbingPaths property in runtimeconfig.json,
+    #   - adding a --deps deps.json option,
+    # cf. <https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet#options-for-running-an-application>
+    # and <https://github.com/dotnet/runtime/blob/main/docs/design/features/host-probing.md>.
+    tmpfiles=
+    if test -n "$libdirs_dotnet"; then
+      # Make sure the added DLLs are removed when this script terminates.
+      func_cleanup_tmpfiles()
+      {
+        saved_IFS="$IFS"
+        IFS='|'
+        for file in $tmpfiles; do
+          IFS="$saved_IFS"
+          rm -f "$file"
+        done
+        IFS="$saved_IFS"
+      }
+      trap func_cleanup_tmpfiles HUP INT QUIT PIPE TERM
+      trap 'exit_status=$?; func_cleanup_tmpfiles; exit $exit_status' EXIT
+      # Copy the DLLs.
+      prog_dir=`dirname "$prog"`
+      saved_IFS="$IFS"
+      IFS='|'
+      for dir in $libdirs_dotnet; do
+        IFS="$saved_IFS"
+        for file in `cd "$dir" && echo *.dll`; do
+          if test -f "$prog_dir/$file"; then
+            # A DLL of this name is already at the expected location.
+            :
+          else
+            tmpfiles="${tmpfiles:+$tmpfiles|}$prog_dir/$file"
+            cp "$dir/$file" "$prog_dir/$file" || exit 1
+          fi
+        done
+      done
+      IFS="$saved_IFS"
+    fi
+    if test -f "${prog%.exe}.runtimeconfig.json"; then
+      # There is already a FOO.runtimeconfig.json alongside FOO.exe.
+      dotnet exec "$prog_arg" "$@"
+      result=$?
+    else
+      # dotnet needs a FOO.runtimeconfig.json alongside FOO.exe in order to
+      # execute FOO.exe.  Create a dummy one in a temporary directory
+      # (because the directory where FOO.exe sits is not necessarily writable).
+      # Documentation of this file format:
+      # <https://learn.microsoft.com/en-us/dotnet/core/runtime-config/>
+      func_tmpdir
+      runtimeconfig="$tmp"/runtimeconfig.json
+      dotnet --list-runtimes | sed -n -e 's/Microsoft.NETCore.App \([^ ]*\) .*/{ "runtimeOptions": { "framework": { "name": "Microsoft.NETCore.App", "version": "\1" } } }/p' > "$runtimeconfig"
+      runtimeconfig_arg="$runtimeconfig"
+      case "@build_os@" in
+        cygwin*)
+          runtimeconfig_arg=`cygpath -w "$runtimeconfig"`
+          ;;
+      esac
+      test -z "$CSHARP_VERBOSE" || echo dotnet exec --runtimeconfig "$runtimeconfig_arg" "$prog_arg" "$@"
+      dotnet exec --runtimeconfig "$runtimeconfig_arg" "$prog_arg" "$@"
+      result=$?
+      rm -f "$runtimeconfig"
+      rmdir "$tmp"
+    fi
+    exit $result
   else
-    echo 'C# virtual machine not found, try installing mono, then reconfigure' 1>&2
-    exit 1
+    if test -n "@HAVE_CLIX@"; then
+      CONF_CLIX_PATH='@CLIX_PATH@'
+      if test -n "$libdirs_mono"; then
+        @CLIX_PATH_VAR@="$libdirs_mono${CONF_CLIX_PATH:+@MONO_PATH_SEPARATOR@$CONF_CLIX_PATH}"
+      else
+        @CLIX_PATH_VAR@="$CONF_CLIX_PATH"
+      fi
+      export @CLIX_PATH_VAR@
+      shift
+      # On Windows, assume that 'clix' is a native Windows program,
+      # not a Cygwin program.
+      case "@build_os@" in
+        cygwin*)
+          prog=`cygpath -w "$prog"`
+          ;;
+      esac
+      test -z "$CSHARP_VERBOSE" || echo clix "$prog" "$@"
+      exec clix "$prog" "$@"
+    else
+      echo 'C# virtual machine not found, try installing mono or dotnet, then reconfigure' 1>&2
+      exit 1
+    fi
   fi
 fi
diff --git a/m4/csharpexec.m4 b/m4/csharpexec.m4
index 8191579f52..a47ad7de29 100644
--- a/m4/csharpexec.m4
+++ b/m4/csharpexec.m4
@@ -1,5 +1,5 @@
 # csharpexec.m4
-# serial 9
+# serial 10
 dnl Copyright (C) 2003-2005, 2009-2024 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -8,7 +8,7 @@
 # Prerequisites of csharpexec.sh.
 # Checks for a C# execution engine.
 # gt_CSHARPEXEC or gt_CSHARPEXEC(testexecutable, its-directory)
-# Sets at most one of HAVE_MONO, HAVE_CLIX.
+# Sets at most one of HAVE_MONO, HAVE_DOTNET, HAVE_CLIX.
 # Sets HAVE_CSHARPEXEC to nonempty if csharpexec.sh will work.
 AC_DEFUN([gt_CSHARPEXEC],
 [
@@ -25,11 +25,12 @@ AC_DEFUN([gt_CSHARPEXEC]
   pushdef([AC_CHECKING],[:])dnl
   pushdef([AC_MSG_RESULT],[:])dnl
   AC_CHECK_PROG([HAVE_MONO_IN_PATH], [mono], [yes])
+  AC_CHECK_PROG([HAVE_DOTNET_IN_PATH], [dotnet], [yes])
   AC_CHECK_PROG([HAVE_CLIX_IN_PATH], [clix], [yes])
   popdef([AC_MSG_RESULT])dnl
   popdef([AC_CHECKING])dnl
   popdef([AC_MSG_CHECKING])dnl
-  for impl in "$CSHARP_CHOICE" mono no; do
+  for impl in "$CSHARP_CHOICE" mono dotnet no; do
     case "$impl" in
       mono)
         if test -n "$HAVE_MONO_IN_PATH" \
@@ -40,6 +41,14 @@ AC_DEFUN([gt_CSHARPEXEC]
           break
         fi
         ;;
+      dotnet)
+        if test -n "$HAVE_DOTNET_IN_PATH" \
+           && dotnet --list-runtimes >/dev/null 2>/dev/null; then
+          HAVE_DOTNET=1
+          ac_result="dotnet"
+          break
+        fi
+        ;;
       sscli)
         if test -n "$HAVE_CLIX_IN_PATH" \
            m4_if([$1], , , [&& clix $2/$1 >/dev/null 2>/dev/null]); then
@@ -73,5 +82,6 @@ AC_DEFUN([gt_CSHARPEXEC]
   AC_SUBST([CLIX_PATH_VAR])
   AC_SUBST([CLIX_PATH])
   AC_SUBST([HAVE_MONO])
+  AC_SUBST([HAVE_DOTNET])
   AC_SUBST([HAVE_CLIX])
 ])
-- 
2.34.1

>From a300496564ad2abcd9b65b98d2f8f474ee2a4b24 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 9 Oct 2024 03:14:04 +0200
Subject: [PATCH 5/7] csharpexec: Add support for dotnet.

* lib/csharpexec.c: Include <dirent.h>, <errno.h>, <sys/stat.h>,
dirname.h, concat-filename.h, canonicalize.h, spawn-pipe.h,
wait-process.h, xalloc.h, copy-file.h, clean-temp-simple.h,
clean-temp.h.
(name_is_dll, execute_csharp_using_dotnet): New functions.
(execute_csharp_program): Invoke execute_csharp_using_dotnet.
* modules/csharpexec (Depends-on): Add stat, dirname, xconcat-filename,
canonicalize, spawn-pipe, wait-process, scandir, alphasort, copy-file,
clean-temp-simple, clean-temp.
---
 ChangeLog          |  11 ++
 lib/csharpexec.c   | 427 ++++++++++++++++++++++++++++++++++++++++++++-
 modules/csharpexec |  13 +-
 3 files changed, 445 insertions(+), 6 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 441bdbb7fb..8e3a411649 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,16 @@
 2024-10-08  Bruno Haible  <br...@clisp.org>
 
+	csharpexec: Add support for dotnet.
+	* lib/csharpexec.c: Include <dirent.h>, <errno.h>, <sys/stat.h>,
+	dirname.h, concat-filename.h, canonicalize.h, spawn-pipe.h,
+	wait-process.h, xalloc.h, copy-file.h, clean-temp-simple.h,
+	clean-temp.h.
+	(name_is_dll, execute_csharp_using_dotnet): New functions.
+	(execute_csharp_program): Invoke execute_csharp_using_dotnet.
+	* modules/csharpexec (Depends-on): Add stat, dirname, xconcat-filename,
+	canonicalize, spawn-pipe, wait-process, scandir, alphasort, copy-file,
+	clean-temp-simple, clean-temp.
+
 	csharpexec-script: Add support for dotnet.
 	* m4/csharpexec.m4 (gt_CSHARPEXEC): Support 'dotnet' as implementation.
 	Set HAVE_DOTNET.
diff --git a/lib/csharpexec.c b/lib/csharpexec.c
index c7e3fe5ae5..79f2a3fd91 100644
--- a/lib/csharpexec.c
+++ b/lib/csharpexec.c
@@ -21,14 +21,26 @@
 /* Specification.  */
 #include "csharpexec.h"
 
+#include <dirent.h>
+#include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <sys/stat.h>
 
 #include <error.h>
+#include "dirname.h"
+#include "concat-filename.h"
+#include "canonicalize.h"
 #include "cygpath.h"
 #include "execute.h"
+#include "spawn-pipe.h"
+#include "wait-process.h"
 #include "sh-quote.h"
+#include "xalloc.h"
 #include "xmalloca.h"
+#include "copy-file.h"
+#include "clean-temp-simple.h"
+#include "clean-temp.h"
 #include "gettext.h"
 
 /* Handling of MONO_PATH is just like Java CLASSPATH.  */
@@ -72,20 +84,33 @@
    Program    from
 
    mono       mono
-   clix       sscli
+   dotnet     dotnet or MSVC
+   clix       sscli  (non-free)
 
    With Mono, the MONO_PATH is a colon separated list of pathnames. (On
    Windows: semicolon separated list of pathnames.)
 
    We try the CIL interpreters in the following order:
-     1. "mono", because it is a partially free system but doesn't integrate
-        well with Unix.
-     2. "clix", although it is not free, because it is a kind of "reference
+     1. "mono", because it is nowadays the "traditional" C# system on Unix.
+     2. "dotnet", because it is another (newer) free C# system on Unix.
+     3. "clix", although it is not free, because it is a kind of "reference
         implementation" of C#.
    But the order can be changed through the --enable-csharp configuration
    option.
  */
 
+static int
+name_is_dll (const struct dirent *d)
+{
+  if (d->d_name[0] != '.')
+    {
+      size_t d_name_len = strlen (d->d_name);
+      if (d_name_len > 4 && memcmp (d->d_name + d_name_len - 4, ".dll", 4) == 0)
+        return 1;
+    }
+  return 0;
+}
+
 static int
 execute_csharp_using_mono (const char *assembly_path,
                            const char * const *libdirs,
@@ -150,6 +175,383 @@ execute_csharp_using_mono (const char *assembly_path,
     return -1;
 }
 
+static int
+execute_csharp_using_dotnet (const char *assembly_path,
+                             const char * const *libdirs,
+                             unsigned int libdirs_count,
+                             const char * const *args, unsigned int nargs,
+                             bool verbose, bool quiet,
+                             execute_fn *executer, void *private_data)
+{
+  static bool dotnet_tested;
+  static bool dotnet_present;
+
+  if (!dotnet_tested)
+    {
+      /* Test for presence of dotnet:
+         "dotnet --list-runtimes >/dev/null 2>/dev/null"  */
+      const char *argv[3];
+      int exitstatus;
+
+      argv[0] = "dotnet";
+      argv[1] = "--list-runtimes";
+      argv[2] = NULL;
+      exitstatus = execute ("dotnet", "dotnet", argv, NULL,
+                            false, false, true, true,
+                            true, false, NULL);
+      dotnet_present = (exitstatus == 0);
+      dotnet_tested = true;
+    }
+
+  if (dotnet_present)
+    {
+      bool err = false;
+
+      char *assembly_path_converted = cygpath_w (assembly_path);
+
+      /* Handle the -L options.
+         The way this works is that we have to copy (or symlink) the DLLs into
+         the directory where FOO.exe resides.
+         Maybe there is another way to do this, but I haven't found it, trying
+           - the --additionalprobingpath command-line option,
+           - the additionalProbingPaths property in runtimeconfig.json,
+           - adding a --deps deps.json option,
+         cf. <https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet#options-for-running-an-application>
+         and <https://github.com/dotnet/runtime/blob/main/docs/design/features/host-probing.md>.  */
+      char **tmpfiles = NULL;
+      size_t tmpfiles_alloc = 0;
+      size_t tmpfiles_count = 0;
+      if (libdirs_count > 0)
+        {
+          /* Copy the DLLs.  */
+          char *assembly_dir = dir_name (assembly_path);
+
+          unsigned int l;
+          for (l = 0; l < libdirs_count; l++)
+            {
+              const char *libdir = libdirs[l];
+
+              struct dirent **dlls;
+              int num_dlls;
+
+              /* Get a list of all *.dll files in libdir.  */
+              num_dlls = scandir (libdir, &dlls, name_is_dll, alphasort);
+              if (num_dlls < 0 && errno == ENOMEM)
+                xalloc_die ();
+              if (num_dlls <= 0)
+                {
+                  dlls = NULL;
+                  num_dlls = 0;
+                }
+
+              int i;
+              for (i = 0; i < num_dlls; i++)
+                {
+                  char *target_dll =
+                    xconcatenated_filename (assembly_dir, dlls[i]->d_name,
+                                            NULL);
+                  struct stat statbuf;
+                  if (stat (target_dll, &statbuf) == 0 || errno == EOVERFLOW)
+                    {
+                      /* A DLL of this name is already at the expected
+                         location.  */
+                    }
+                  else
+                    {
+                      char *absolute_target_dll =
+                        canonicalize_filename_mode (target_dll, CAN_ALL_BUT_LAST);
+                      if (absolute_target_dll == NULL)
+                        {
+                          if (errno == ENOMEM)
+                            xalloc_die ();
+                        }
+                      else
+                        {
+                          char *libdir_dll =
+                            xconcatenated_filename (libdir, dlls[i]->d_name,
+                                                    NULL);
+
+                          /* Create absolute_target_dll as a temporary file.  */
+                          if (register_temporary_file (absolute_target_dll) < 0)
+                            xalloc_die ();
+
+                          if (tmpfiles_count >= tmpfiles_alloc)
+                            {
+                              tmpfiles_alloc = 2 * tmpfiles_alloc + 1;
+                              tmpfiles =
+                                (char **)
+                                xrealloc (tmpfiles, tmpfiles_alloc * sizeof (char *));
+                            }
+                          tmpfiles[tmpfiles_count++] = absolute_target_dll;
+
+                          if (copy_file_to (libdir_dll, absolute_target_dll) != 0)
+                            {
+                              remove (absolute_target_dll);
+                              unregister_temporary_file (absolute_target_dll);
+                              error (0, 0, _("failed to copy '%s' to '%s'"),
+                                     libdir_dll, absolute_target_dll);
+                              free (libdir_dll);
+                              free (target_dll);
+                              for (i = 0; i < num_dlls; i++)
+                                free (dlls[i]);
+                              free (dlls);
+                              free (assembly_dir);
+                              while (tmpfiles_count > 0)
+                                {
+                                  char *tmpfile = tmpfiles[--tmpfiles_count];
+                                  remove (tmpfile);
+                                  unregister_temporary_file (tmpfile);
+                                  free (tmpfile);
+                                }
+                              free (tmpfiles);
+                              return 1;
+                            }
+                          free (libdir_dll);
+                        }
+                    }
+                  free (target_dll);
+                }
+
+              for (i = 0; i < num_dlls; i++)
+                free (dlls[i]);
+              free (dlls);
+            }
+
+          free (assembly_dir);
+        }
+
+      /* Test whether alongside FOO.exe, a file FOO.runtimeconfig.json already
+         exists.  */
+      char *runtimeconfig_filename =
+        (char *) xmalloca (strlen (assembly_path) + 19 + 1);
+      {
+        size_t assembly_path_len = strlen (assembly_path);
+        if (assembly_path_len > 4
+            && memcmp (assembly_path + (assembly_path_len - 4), ".exe", 4) == 0)
+          assembly_path_len -= 4;
+        char *p = runtimeconfig_filename;
+        memcpy (p, assembly_path, assembly_path_len);
+        p += assembly_path_len;
+        strcpy (p, ".runtimeconfig.json");
+      }
+      struct stat runtimeconfig_statbuf;
+      if (stat (runtimeconfig_filename, &runtimeconfig_statbuf) == 0
+          || errno == EOVERFLOW)
+        {
+          const char **argv =
+            (const char **) xmalloca ((3 + nargs + 1) * sizeof (const char *));
+          unsigned int i;
+
+          argv[0] = "dotnet";
+          argv[1] = "exec";
+          argv[2] = assembly_path_converted;
+          for (i = 0; i <= nargs; i++)
+            argv[3 + i] = args[i];
+
+          if (verbose)
+            {
+              char *command = shell_quote_argv (argv);
+              printf ("%s\n", command);
+              free (command);
+            }
+
+          err = executer ("dotnet", "dotnet", argv, private_data);
+
+          freea (argv);
+        }
+      else
+        {
+          /* dotnet needs a FOO.runtimeconfig.json alongside FOO.exe in order to
+             execute FOO.exe.  Create a dummy one in a temporary directory
+             (because the directory where FOO.exe sits is not necessarily
+             writable).
+             Documentation of this file format:
+             <https://learn.microsoft.com/en-us/dotnet/core/runtime-config/>  */
+
+          char *netcore_version;
+
+          /* Invoke 'dotnet --list-runtimes' and extract the .NET Core version
+             from its output.  */
+          {
+            netcore_version = NULL;
+
+            const char *argv[3];
+            argv[0] = "dotnet";
+            argv[1] = "--list-runtimes";
+            argv[2] = NULL;
+
+            /* Open a pipe to the program.  */
+            int fd[1];
+            pid_t child = create_pipe_in ("dotnet", "dotnet", argv, NULL,
+                                          DEV_NULL, false, true, false, fd);
+            if (child == -1)
+              {
+                error (0, 0, _("%s subprocess I/O error"), "dotnet");
+                err = true;
+              }
+            else
+              {
+                /* Retrieve its result.  */
+                FILE *fp = fdopen (fd[0], "r");
+                if (fp == NULL)
+                  error (EXIT_FAILURE, errno, _("fdopen() failed"));
+
+                char *line = NULL;
+                size_t linesize = 0;
+
+                for (;;)
+                  {
+                    size_t linelen = getline (&line, &linesize, fp);
+                    if (linelen == (size_t)(-1))
+                      {
+                        error (0, 0, _("%s subprocess I/O error"), "dotnet");
+                        err = true;
+                        break;
+                      }
+                    if (linelen > 0 && line[linelen - 1] == '\n')
+                      {
+                        line[linelen - 1] = '\0';
+                        linelen--;
+                        if (linelen > 0 && line[linelen - 1] == '\r')
+                          {
+                            line[linelen - 1] = '\0';
+                            linelen--;
+                          }
+                      }
+                    /* The line has the structure
+                         Microsoft.SUBSYSTEM VERSION [DIRECTORY]  */
+                    if (linelen > 22
+                        && memcmp (line, "Microsoft.NETCore.App ", 22) == 0)
+                      {
+                        char *version = line + 22;
+                        char *version_end = strchr (version, ' ');
+                        if (version_end != NULL)
+                          {
+                            *version_end = '\0';
+                            netcore_version = xstrdup (version);
+                            break;
+                          }
+                      }
+                  }
+
+                free (line);
+
+                /* Read until EOF (otherwise the child process may get a
+                   SIGPIPE signal).  */
+                while (getc (fp) != EOF)
+                  ;
+
+                fclose (fp);
+
+                /* Remove zombie process from process list, and retrieve
+                   exit status.  */
+                int exitstatus =
+                  wait_subprocess (child, "dotnet", true, false, true, false,
+                                   NULL);
+                if (exitstatus != 0)
+                  {
+                    error (0, 0, _("%s subprocess failed"), "dotnet");
+                    err = true;
+                  }
+              }
+          }
+
+          if (!err)
+            {
+              if (netcore_version == NULL)
+                {
+                  error (0, 0, _("could not determine %s version"), "dotnet");
+                  err = true;
+                }
+            }
+
+          if (!err)
+            {
+              /* Create the runtimeconfig.json file.  */
+              struct temp_dir *tmpdir = create_temp_dir ("csharp", NULL, false);
+              if (tmpdir == NULL)
+                err = true;
+              else
+                {
+                  char *runtimeconfig =
+                    xconcatenated_filename (tmpdir->dir_name,
+                                            "runtimeconfig.json", NULL);
+                  register_temp_file (tmpdir, runtimeconfig);
+                  FILE *fp = fopen_temp (runtimeconfig, "w", false);
+                  if (fp == NULL)
+                    {
+                      error (0, errno, _("failed to create \"%s\""), runtimeconfig);
+                      unregister_temp_file (tmpdir, runtimeconfig);
+                      err = true;
+                    }
+                  else
+                    {
+                      fprintf (fp,
+                               "{ \"runtimeOptions\":\n"
+                               "  { \"framework\":\n"
+                               "    { \"name\": \"Microsoft.NETCore.App\", \"version\": \"%s\" }\n"
+                               "  }\n"
+                               "}",
+                               netcore_version);
+                      if (fwriteerror_temp (fp))
+                        {
+                          error (0, errno, _("error while writing \"%s\" file"),
+                                 runtimeconfig);
+                          err = true;
+                        }
+                      else
+                        {
+                          char *runtimeconfig_converted =
+                            cygpath_w (runtimeconfig);
+                          const char **argv =
+                            (const char **)
+                            xmalloca ((5 + nargs + 1) * sizeof (const char *));
+                          unsigned int i;
+
+                          argv[0] = "dotnet";
+                          argv[1] = "exec";
+                          argv[2] = "--runtimeconfig";
+                          argv[3] = runtimeconfig_converted;
+                          argv[4] = assembly_path_converted;
+                          for (i = 0; i <= nargs; i++)
+                            argv[5 + i] = args[i];
+
+                          if (verbose)
+                            {
+                              char *command = shell_quote_argv (argv);
+                              printf ("%s\n", command);
+                              free (command);
+                            }
+
+                          err = executer ("dotnet", "dotnet", argv,
+                                          private_data);
+
+                          freea (argv);
+                          free (runtimeconfig_converted);
+                        }
+                    }
+                  free (runtimeconfig);
+                  cleanup_temp_dir (tmpdir);
+                }
+            }
+        }
+
+      freea (runtimeconfig_filename);
+      while (tmpfiles_count > 0)
+        {
+          char *tmpfile = tmpfiles[--tmpfiles_count];
+          remove (tmpfile);
+          unregister_temporary_file (tmpfile);
+          free (tmpfile);
+        }
+      free (tmpfiles);
+      free (assembly_path_converted);
+      return err;
+    }
+  else
+    return -1;
+}
+
 static int
 execute_csharp_using_sscli (const char *assembly_path,
                             const char * const *libdirs,
@@ -244,6 +646,13 @@ execute_csharp_program (const char *assembly_path,
   if (result >= 0)
     return (bool) result;
 #endif
+#if CSHARP_CHOICE_DOTNET
+  result = execute_csharp_using_dotnet (assembly_path, libdirs, libdirs_count,
+                                        args, nargs, verbose, quiet,
+                                        executer, private_data);
+  if (result >= 0)
+    return (bool) result;
+#endif
 
   /* Then try the remaining C# implementations in our standard order.  */
 #if !CSHARP_CHOICE_MONO
@@ -254,6 +663,14 @@ execute_csharp_program (const char *assembly_path,
     return (bool) result;
 #endif
 
+#if !CSHARP_CHOICE_DOTNET
+  result = execute_csharp_using_dotnet (assembly_path, libdirs, libdirs_count,
+                                        args, nargs, verbose, quiet,
+                                        executer, private_data);
+  if (result >= 0)
+    return (bool) result;
+#endif
+
   result = execute_csharp_using_sscli (assembly_path, libdirs, libdirs_count,
                                        args, nargs, verbose, quiet,
                                        executer, private_data);
@@ -261,6 +678,6 @@ execute_csharp_program (const char *assembly_path,
     return (bool) result;
 
   if (!quiet)
-    error (0, 0, _("C# virtual machine not found, try installing mono"));
+    error (0, 0, _("C# virtual machine not found, try installing mono or dotnet"));
   return true;
 }
diff --git a/modules/csharpexec b/modules/csharpexec
index 11dcf49edb..6559c0e195 100644
--- a/modules/csharpexec
+++ b/modules/csharpexec
@@ -9,13 +9,24 @@ lib/classpath.c
 
 Depends-on:
 stdbool
+stat
+error
+dirname
+xconcat-filename
+canonicalize
 cygpath
 execute
+spawn-pipe
+wait-process
 xsetenv
+scandir
+alphasort
 sh-quote
 xalloc
 xmalloca
-error
+copy-file
+clean-temp-simple
+clean-temp
 gettext-h
 csharpexec-script
 
-- 
2.34.1

>From 4d69eebfa32552c71922579feaf702e7896f2532 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 9 Oct 2024 03:14:26 +0200
Subject: [PATCH 6/7] csharpcomp-script: Add support for dotnet.

* m4/csharpcomp.m4 (gt_CSHARPCOMP): Support 'dotnet' as implementation.
Set HAVE_DOTNET_SDK, HAVE_DOTNET_CSC.
* build-aux/csharpcomp.sh.in: Add implementations for the cases
$HAVE_DOTNET_SDK = 1 and $HAVE_DOTNET_CSC = 1.
---
 ChangeLog                  |  8 ++++++
 build-aux/csharpcomp.sh.in | 50 ++++++++++++++++++++++++++++----------
 m4/csharpcomp.m4           | 35 ++++++++++++++++++++++++--
 3 files changed, 78 insertions(+), 15 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 8e3a411649..6c1bf09679 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,11 @@
+2024-10-08  Bruno Haible  <br...@clisp.org>
+
+	csharpcomp-script: Add support for dotnet.
+	* m4/csharpcomp.m4 (gt_CSHARPCOMP): Support 'dotnet' as implementation.
+	Set HAVE_DOTNET_SDK, HAVE_DOTNET_CSC.
+	* build-aux/csharpcomp.sh.in: Add implementations for the cases
+	$HAVE_DOTNET_SDK = 1 and $HAVE_DOTNET_CSC = 1.
+
 2024-10-08  Bruno Haible  <br...@clisp.org>
 
 	csharpexec: Add support for dotnet.
diff --git a/build-aux/csharpcomp.sh.in b/build-aux/csharpcomp.sh.in
index 9febcddd72..85ea5aaf22 100644
--- a/build-aux/csharpcomp.sh.in
+++ b/build-aux/csharpcomp.sh.in
@@ -82,8 +82,8 @@ while test $# != 0; do
           ;;
       esac
       options_mcs="$options_mcs -out:"`echo "$2" | sed -e "$sed_quote_subst"`
-      # On Windows, assume that 'csc' is a native Windows program,
-      # not a Cygwin program.
+      # On Windows, assume that 'dotnet' and 'csc' are native Windows programs,
+      # not Cygwin programs.
       arg="$2"
       case "@build_os@" in
         cygwin*)
@@ -95,8 +95,8 @@ while test $# != 0; do
       ;;
     -L)
       options_mcs="$options_mcs -lib:"`echo "$2" | sed -e "$sed_quote_subst"`
-      # On Windows, assume that 'csc' is a native Windows program,
-      # not a Cygwin program.
+      # On Windows, assume that 'dotnet' and 'csc' are native Windows programs,
+      # not Cygwin programs.
       arg="$2"
       case "@build_os@" in
         cygwin*)
@@ -124,8 +124,8 @@ while test $# != 0; do
       ;;
     *.resources)
       options_mcs="$options_mcs -resource:"`echo "$1" | sed -e "$sed_quote_subst"`
-      # On Windows, assume that 'csc' is a native Windows program,
-      # not a Cygwin program.
+      # On Windows, assume that 'dotnet' and 'csc' are native Windows programs,
+      # not Cygwin programs.
       arg="$1"
       case "@build_os@" in
         cygwin*)
@@ -136,8 +136,8 @@ while test $# != 0; do
       ;;
     *.cs)
       sources="$sources "`echo "$1" | sed -e "$sed_quote_subst"`
-      # On Windows, assume that 'csc' is a native Windows program,
-      # not a Cygwin program.
+      # On Windows, assume that 'dotnet' and 'csc' are native Windows programs,
+      # not Cygwin programs.
       arg="$1"
       case "@build_os@" in
         cygwin*)
@@ -169,11 +169,35 @@ if test -n "@HAVE_MCS@"; then
   rm -rf "$tmp"
   exit $result
 else
-  if test -n "@HAVE_CSC@"; then
-    test -z "$CSHARP_VERBOSE" || echo csc $options_csc $sources_csc
-    exec csc $options_csc $sources_csc
+  if test -n "@HAVE_DOTNET_SDK@"; then
+    dotnet_runtime_dir=`dotnet --list-runtimes | sed -n -e 's/Microsoft.NETCore.App \([^ ]*\) \[\(.*\)\].*/\2\/\1/p' | sed -e 1q`
+    dotnet_sdk_dir=`dotnet --list-sdks | sed -e 's/\([^ ]*\) \[\(.*\)\].*/\2\/\1/p' | sed -e 1q`
+    # Add -lib and -reference options, so that the compiler finds Object, Console, String, etc.
+    arg="$dotnet_runtime_dir"
+    case "@build_os@" in
+      cygwin*)
+        arg=`cygpath -w "$arg"`
+        ;;
+    esac
+    options_csc="$options_csc -lib:"`echo "$arg" | sed -e "$sed_quote_subst"`
+    for file in `cd "$dotnet_runtime_dir" && echo *.dll`; do
+      options_csc="$options_csc -reference:"`echo "$file" | sed -e "$sed_quote_subst"`
+    done
+    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
+    exec dotnet "$csc" $options_csc $sources_csc
   else
-    echo 'C# compiler not found, try installing mono, then reconfigure' 1>&2
-    exit 1
+    if test -n "@HAVE_DOTNET_CSC@" || test -n "@HAVE_CSC@"; then
+      test -z "$CSHARP_VERBOSE" || echo csc $options_csc $sources_csc
+      exec csc $options_csc $sources_csc
+    else
+      echo 'C# compiler not found, try installing mono or dotnet, then reconfigure' 1>&2
+      exit 1
+    fi
   fi
 fi
diff --git a/m4/csharpcomp.m4 b/m4/csharpcomp.m4
index d9ebd53f6b..8465e85d77 100644
--- a/m4/csharpcomp.m4
+++ b/m4/csharpcomp.m4
@@ -1,5 +1,5 @@
 # csharpcomp.m4
-# serial 9
+# serial 10
 dnl Copyright (C) 2003-2005, 2007, 2009-2024 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -7,7 +7,7 @@
 
 # Prerequisites of csharpcomp.sh.
 # Checks for a C# compiler.
-# Sets at most one of HAVE_MCS, HAVE_CSC.
+# Sets at most one of HAVE_MCS, HAVE_DOTNET_SDK, HAVE_DOTNET_CSC, HAVE_CSC.
 # Sets HAVE_CSHARPCOMP to nonempty if csharpcomp.sh will work.
 # Also sets CSHARPCOMPFLAGS.
 AC_DEFUN([gt_CSHARPCOMP],
@@ -19,6 +19,7 @@ AC_DEFUN([gt_CSHARPCOMP]
   pushdef([AC_CHECKING],[:])dnl
   pushdef([AC_MSG_RESULT],[:])dnl
   AC_CHECK_PROG([HAVE_MCS_IN_PATH], [mcs], [yes])
+  AC_CHECK_PROG([HAVE_DOTNET_IN_PATH], [dotnet], [yes])
   AC_CHECK_PROG([HAVE_CSC_IN_PATH], [csc], [yes])
   popdef([AC_MSG_RESULT])dnl
   popdef([AC_CHECKING])dnl
@@ -34,6 +35,34 @@ AC_DEFUN([gt_CSHARPCOMP]
           break
         fi
         ;;
+      dotnet)
+        # The dotnet compiler is called "Roslyn".
+        # <https://en.wikipedia.org/wiki/Roslyn_(compiler)>
+        # There are two situations:
+        # - A dotnet SDK, that contains a 'dotnet' program and the Roslyn
+        #   compiler as csc.dll.
+        # - An MSVC installation, that contains the Roslyn compiler as csc.exe
+        #   (e.g. in C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\Roslyn\csc.exe).
+        # In the first case, the user only has to make sure that 'dotnet' is
+        # found in $PATH.
+        # In the second case, they need to make sure that both 'dotnet' and
+        # 'csc' are found in $PATH.
+        if test -n "$HAVE_DOTNET_IN_PATH" \
+           && dotnet --list-runtimes >/dev/null 2>/dev/null \
+           && test -n "`dotnet --list-sdks 2>/dev/null`"; then
+          HAVE_DOTNET_SDK=1
+          ac_result="dotnet"
+          break
+        else
+          if test -n "$HAVE_CSC_IN_PATH" \
+             && csc -help 2>/dev/null | grep analyzer >/dev/null \
+             && { if csc -help 2>/dev/null | grep -i chicken > /dev/null; then false; else true; fi; }; then
+            HAVE_DOTNET_CSC=1
+            ac_result="dotnet"
+            break
+          fi
+        fi
+        ;;
       sscli)
         if test -n "$HAVE_CSC_IN_PATH" \
            && csc -help >/dev/null 2>/dev/null \
@@ -52,6 +81,8 @@ AC_DEFUN([gt_CSHARPCOMP]
   done
   AC_MSG_RESULT([$ac_result])
   AC_SUBST([HAVE_MCS])
+  AC_SUBST([HAVE_DOTNET_SDK])
+  AC_SUBST([HAVE_DOTNET_CSC])
   AC_SUBST([HAVE_CSC])
   dnl Provide a default for CSHARPCOMPFLAGS.
   if test -z "${CSHARPCOMPFLAGS+set}"; then
-- 
2.34.1

>From efa78d8905e1a0260930692fd888d1917a837cd6 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Wed, 9 Oct 2024 03:20:00 +0200
Subject: [PATCH 7/7] csharpcomp: Add support for dotnet.

* lib/csharpcomp.c: Include <dirent.h>, concat-filename.h, xvasprintf.h.
(name_is_dll): New function, from lib/csharpexec.c.
(compile_csharp_using_dotnet): New function.
(compile_csharp_class): Invoke compile_csharp_using_dotnet.
* modules/csharpcomp (Depends-on): Add xconcat-filename, scandir,
alphasort, xvasprintf.
---
 ChangeLog          |   8 +
 lib/csharpcomp.c   | 632 ++++++++++++++++++++++++++++++++++++++++++++-
 modules/csharpcomp |   8 +-
 3 files changed, 636 insertions(+), 12 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 6c1bf09679..5e36a3f4be 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
 2024-10-08  Bruno Haible  <br...@clisp.org>
 
+	csharpcomp: Add support for dotnet.
+	* lib/csharpcomp.c: Include <dirent.h>, concat-filename.h, xvasprintf.h.
+	(name_is_dll): New function, from lib/csharpexec.c.
+	(compile_csharp_using_dotnet): New function.
+	(compile_csharp_class): Invoke compile_csharp_using_dotnet.
+	* modules/csharpcomp (Depends-on): Add xconcat-filename, scandir,
+	alphasort, xvasprintf.
+
 	csharpcomp-script: Add support for dotnet.
 	* m4/csharpcomp.m4 (gt_CSHARPCOMP): Support 'dotnet' as implementation.
 	Set HAVE_DOTNET_SDK, HAVE_DOTNET_CSC.
diff --git a/lib/csharpcomp.c b/lib/csharpcomp.c
index 28247ed1a7..4caed49533 100644
--- a/lib/csharpcomp.c
+++ b/lib/csharpcomp.c
@@ -21,12 +21,14 @@
 /* Specification.  */
 #include "csharpcomp.h"
 
+#include <dirent.h>
 #include <errno.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
 #include <error.h>
+#include "concat-filename.h"
 #include "cygpath.h"
 #include "execute.h"
 #include "spawn-pipe.h"
@@ -34,6 +36,7 @@
 #include "sh-quote.h"
 #include "safe-read.h"
 #include "xmalloca.h"
+#include "xvasprintf.h"
 #include "gettext.h"
 
 #define _(str) gettext (str)
@@ -41,22 +44,35 @@
 
 /* Survey of C# compilers.
 
-   Program    from
+   Program          from
 
-   mcs        mono
-   csc        sscli
+   mcs              mono
+   dotnet csc.dll   dotnet
+   csc              MSVC
+   csc              sscli  (non-free)
 
-   We try the CIL interpreters in the following order:
-     1. "mcs", because it is a free system but doesn't integrate so well
-        with Unix. (Command line options start with / instead of -. Errors go
-        to stdout instead of stderr. Source references are printed as
-        "file(lineno)" instead of "file:lineno:".)
-     2. "csc", although it is not free, because it is a kind of "reference
+   We try the C# compilers in the following order:
+     1. "mcs", because it is nowadays the "traditional" C# system on Unix.
+     2. "dotnet" or "csc" from MSVC, because it is another (newer) free C#
+        system.
+     3. "csc", although it is not free, because it is a kind of "reference
         implementation" of C#.
    But the order can be changed through the --enable-csharp configuration
    option.
  */
 
+static int
+name_is_dll (const struct dirent *d)
+{
+  if (d->d_name[0] != '.')
+    {
+      size_t d_name_len = strlen (d->d_name);
+      if (d_name_len > 4 && memcmp (d->d_name + d_name_len - 4, ".dll", 4) == 0)
+        return 1;
+    }
+  return 0;
+}
+
 static int
 compile_csharp_using_mono (const char * const *sources,
                            unsigned int sources_count,
@@ -245,6 +261,583 @@ compile_csharp_using_mono (const char * const *sources,
     return -1;
 }
 
+static int
+compile_csharp_using_dotnet (const char * const *sources,
+                             unsigned int sources_count,
+                             const char * const *libdirs,
+                             unsigned int libdirs_count,
+                             const char * const *libraries,
+                             unsigned int libraries_count,
+                             const char *output_file, bool output_is_library,
+                             bool optimize, bool debug,
+                             bool verbose)
+{
+  static bool dotnet_tested;
+  static bool dotnet_present;
+
+  if (!dotnet_tested)
+    {
+      /* Test for presence of dotnet:
+         dotnet --list-runtimes >/dev/null 2>/dev/null
+         && test -n "`dotnet --list-sdks 2>/dev/null`"  */
+      int exitstatus;
+      {
+        const char *argv[3];
+
+        argv[0] = "dotnet";
+        argv[1] = "--list-runtimes";
+        argv[2] = NULL;
+        exitstatus = execute ("dotnet", "dotnet", argv, NULL,
+                              false, false, true, true,
+                              true, false, NULL);
+      }
+      if (exitstatus == 0)
+        {
+          const char *argv[3];
+          pid_t child;
+          int fd[1];
+
+          argv[0] = "dotnet";
+          argv[1] = "--list-sdks";
+          argv[2] = NULL;
+          child = create_pipe_in ("dotnet", "dotnet", argv, NULL,
+                                  DEV_NULL, true, true, false, fd);
+          if (child != -1)
+            {
+              /* Read the subprocess output, and test whether it is
+                 non-empty.  */
+              size_t count = 0;
+              char c;
+
+              while (safe_read (fd[0], &c, 1) > 0)
+                count++;
+
+              close (fd[0]);
+
+              /* Remove zombie process from process list, and retrieve exit
+                 status.  */
+              exitstatus =
+                wait_subprocess (child, "dotnet", false, true, true, false,
+                                 NULL);
+              dotnet_present = (exitstatus == 0 && count > 0);
+            }
+          else
+            dotnet_present = false;
+        }
+      else
+        dotnet_present = false;
+      dotnet_tested = true;
+    }
+
+  if (dotnet_present)
+    {
+      bool err = false;
+
+      char *dotnet_runtime_dir;
+      char *dotnet_sdk_dir;
+
+      /* Invoke 'dotnet --list-runtimes' and extract the .NET runtime dir
+         from its output.  */
+      {
+        dotnet_runtime_dir = NULL;
+
+        const char *argv[3];
+        argv[0] = "dotnet";
+        argv[1] = "--list-runtimes";
+        argv[2] = NULL;
+
+        /* Open a pipe to the program.  */
+        int fd[1];
+        pid_t child = create_pipe_in ("dotnet", "dotnet", argv, NULL,
+                                      DEV_NULL, false, true, false, fd);
+        if (child == -1)
+          {
+            error (0, 0, _("%s subprocess I/O error"), "dotnet");
+            err = true;
+          }
+        else
+          {
+            /* Retrieve its result.  */
+            FILE *fp = fdopen (fd[0], "r");
+            if (fp == NULL)
+              error (EXIT_FAILURE, errno, _("fdopen() failed"));
+
+            char *line = NULL;
+            size_t linesize = 0;
+
+            for (;;)
+              {
+                size_t linelen = getline (&line, &linesize, fp);
+                if (linelen == (size_t)(-1))
+                  {
+                    error (0, 0, _("%s subprocess I/O error"), "dotnet");
+                    err = true;
+                    break;
+                  }
+                if (linelen > 0 && line[linelen - 1] == '\n')
+                  {
+                    line[linelen - 1] = '\0';
+                    linelen--;
+                    if (linelen > 0 && line[linelen - 1] == '\r')
+                      {
+                        line[linelen - 1] = '\0';
+                        linelen--;
+                      }
+                  }
+                /* The line has the structure
+                     Microsoft.SUBSYSTEM VERSION [DIRECTORY]  */
+                if (linelen > 22
+                    && memcmp (line, "Microsoft.NETCore.App ", 22) == 0)
+                  {
+                    char *version = line + 22;
+                    char *version_end = strchr (version, ' ');
+                    if (version_end != NULL && version_end[1] == '[')
+                      {
+                        *version_end = '\0';
+                        char *dir = version_end + 2;
+                        char *dir_end = strchr (dir, ']');
+                        if (dir_end != NULL)
+                          {
+                            *dir_end = '\0';
+                            dotnet_runtime_dir =
+                              xasprintf ("%s/%s", dir, version);
+                            break;
+                          }
+                      }
+                  }
+              }
+
+            free (line);
+
+            /* Read until EOF (otherwise the child process may get a
+               SIGPIPE signal).  */
+            while (getc (fp) != EOF)
+              ;
+
+            fclose (fp);
+
+            /* Remove zombie process from process list, and retrieve
+               exit status.  */
+            int exitstatus =
+              wait_subprocess (child, "dotnet", true, false, true, false, NULL);
+            if (exitstatus != 0)
+              {
+                error (0, 0, _("%s subprocess failed"), "dotnet");
+                err = true;
+              }
+          }
+      }
+
+      /* Invoke 'dotnet --list-sdks' and extract the .NET SDK dir
+         from its output.  */
+      {
+        dotnet_sdk_dir = NULL;
+
+        const char *argv[3];
+        argv[0] = "dotnet";
+        argv[1] = "--list-sdks";
+        argv[2] = NULL;
+
+        /* Open a pipe to the program.  */
+        int fd[1];
+        pid_t child = create_pipe_in ("dotnet", "dotnet", argv, NULL,
+                                      DEV_NULL, false, true, false, fd);
+        if (child == -1)
+          {
+            error (0, 0, _("%s subprocess I/O error"), "dotnet");
+            err = true;
+          }
+        else
+          {
+            /* Retrieve its result.  */
+            FILE *fp = fdopen (fd[0], "r");
+            if (fp == NULL)
+              error (EXIT_FAILURE, errno, _("fdopen() failed"));
+
+            char *line = NULL;
+            size_t linesize = 0;
+
+            for (;;)
+              {
+                size_t linelen = getline (&line, &linesize, fp);
+                if (linelen == (size_t)(-1))
+                  {
+                    error (0, 0, _("%s subprocess I/O error"), "dotnet");
+                    err = true;
+                    break;
+                  }
+                if (linelen > 0 && line[linelen - 1] == '\n')
+                  {
+                    line[linelen - 1] = '\0';
+                    linelen--;
+                    if (linelen > 0 && line[linelen - 1] == '\r')
+                      {
+                        line[linelen - 1] = '\0';
+                        linelen--;
+                      }
+                  }
+                /* The line has the structure
+                     VERSION [DIRECTORY]  */
+                char *version = line;
+                char *version_end = strchr (version, ' ');
+                if (version_end != NULL && version_end[1] == '[')
+                  {
+                    *version_end = '\0';
+                    char *dir = version_end + 2;
+                    char *dir_end = strchr (dir, ']');
+                    if (dir_end != NULL)
+                      {
+                        *dir_end = '\0';
+                        dotnet_sdk_dir = xasprintf ("%s/%s", dir, version);
+                        break;
+                      }
+                  }
+              }
+
+            free (line);
+
+            /* Read until EOF (otherwise the child process may get a
+               SIGPIPE signal).  */
+            while (getc (fp) != EOF)
+              ;
+
+            fclose (fp);
+
+            /* Remove zombie process from process list, and retrieve
+               exit status.  */
+            int exitstatus =
+              wait_subprocess (child, "dotnet", true, false, true, false, NULL);
+            if (exitstatus != 0)
+              {
+                error (0, 0, _("%s subprocess failed"), "dotnet");
+                err = true;
+              }
+          }
+      }
+
+      if (err)
+        return 1;
+
+      struct dirent **dlls;
+      int num_dlls;
+      char *roslyn_dir;
+      char *roslyn_bin_dir;
+      char *csc;
+      char *csc_converted;
+      char **malloced;
+      char **mallocedp;
+      unsigned int argc;
+      const char **argv;
+      const char **argp;
+      int exitstatus;
+      unsigned int i;
+
+      /* Get a list of all *.dll files in dotnet_runtime_dir.  */
+      num_dlls = scandir (dotnet_runtime_dir, &dlls, name_is_dll, alphasort);
+      if (num_dlls < 0 && errno == ENOMEM)
+        xalloc_die ();
+      if (num_dlls <= 0)
+        {
+          dlls = NULL;
+          num_dlls = 0;
+        }
+
+      /* Construct the file name of the 'csc' compiler.  */
+      roslyn_dir = xconcatenated_filename (dotnet_sdk_dir, "Roslyn", NULL);
+      roslyn_bin_dir = xconcatenated_filename (roslyn_dir, "bincore", NULL);
+      csc = xconcatenated_filename (roslyn_bin_dir, "csc.dll", NULL);
+      csc_converted = cygpath_w (csc);
+
+      /* Here, we assume that 'csc' is a native Windows program, therefore
+         we need to use cygpath_w.  */
+      malloced =
+        (char **)
+        xmalloca ((1 + libdirs_count + sources_count * 2 + 1) * sizeof (char *));
+      mallocedp = malloced;
+
+      argc =
+        3 + 1 + 1 + libdirs_count + libraries_count
+        + (optimize ? 1 : 0) + (debug ? 1 : 0) + sources_count + 1 + num_dlls;
+      argv = (const char **) xmalloca ((argc + 1) * sizeof (const char *));
+
+      argp = argv;
+      *argp++ = "dotnet";
+      *argp++ = csc_converted;
+      *argp++ = "-nologo";
+      *argp++ = (output_is_library ? "-target:library" : "-target:exe");
+      {
+        char *output_file_converted = cygpath_w (output_file);
+        *mallocedp++ = output_file_converted;
+        char *option = (char *) xmalloca (5 + strlen (output_file_converted) + 1);
+        memcpy (option, "-out:", 5);
+        strcpy (option + 5, output_file_converted);
+        *argp++ = option;
+      }
+      for (i = 0; i < libdirs_count; i++)
+        {
+          const char *libdir = libdirs[i];
+          char *libdir_converted = cygpath_w (libdir);
+          *mallocedp++ = libdir_converted;
+          char *option = (char *) xmalloca (5 + strlen (libdir_converted) + 1);
+          memcpy (option, "-lib:", 5);
+          strcpy (option + 5, libdir_converted);
+          *argp++ = option;
+        }
+      for (i = 0; i < libraries_count; i++)
+        {
+          char *option = (char *) xmalloca (11 + strlen (libraries[i]) + 4 + 1);
+          memcpy (option, "-reference:", 11);
+          memcpy (option + 11, libraries[i], strlen (libraries[i]));
+          strcpy (option + 11 + strlen (libraries[i]), ".dll");
+          *argp++ = option;
+        }
+      if (optimize)
+        *argp++ = "-optimize+";
+      if (debug)
+        *argp++ = "-debug+";
+      for (i = 0; i < sources_count; i++)
+        {
+          const char *source_file = sources[i];
+          char *source_file_converted = cygpath_w (source_file);
+          *mallocedp++ = source_file_converted;
+          if (strlen (source_file_converted) >= 10
+              && memcmp (source_file_converted
+                         + strlen (source_file_converted) - 10,
+                         ".resources",
+                         10) == 0)
+            {
+              char *option =
+                (char *) xmalloc (10 + strlen (source_file_converted) + 1);
+              memcpy (option, "-resource:", 10);
+              strcpy (option + 10, source_file_converted);
+              *mallocedp++ = option;
+              *argp++ = option;
+            }
+          else
+            *argp++ = source_file_converted;
+        }
+      /* Add -lib and -reference options, so that the compiler finds
+         Object, Console, String, etc.  */
+      {
+        char *dotnet_runtime_dir_converted = cygpath_w (dotnet_runtime_dir);
+        *mallocedp++ = dotnet_runtime_dir_converted;
+        char *option =
+          (char *) xmalloca (5 + strlen (dotnet_runtime_dir_converted) + 1);
+        memcpy (option, "-lib:", 5);
+        strcpy (option + 5, dotnet_runtime_dir_converted);
+        *argp++ = option;
+      }
+      for (i = 0; i < num_dlls; i++)
+        {
+          char *option = (char *) xmalloca (11 + strlen (dlls[i]->d_name) + 1);
+          memcpy (option, "-reference:", 11);
+          strcpy (option + 11, dlls[i]->d_name);
+          *argp++ = option;
+        }
+      *argp = NULL;
+      /* Ensure argv length was correctly calculated.  */
+      if (argp - argv != argc)
+        abort ();
+
+      if (verbose)
+        {
+          char *command = shell_quote_argv (argv);
+          printf ("%s\n", command);
+          free (command);
+        }
+
+      exitstatus = execute ("dotnet", "dotnet", argv, NULL,
+                            false, false, false, false,
+                            true, true, NULL);
+
+      for (i = 4; i < 5 + libdirs_count + libraries_count; i++)
+        freea ((char *) argv[i]);
+      for (i = 0; i < 1 + num_dlls; i++)
+        freea ((char *) argv[argc - (1 + num_dlls) + i]);
+      while (mallocedp > malloced)
+        free (*--mallocedp);
+      freea (argv);
+      freea (malloced);
+      free (csc_converted);
+      free (csc);
+      free (roslyn_bin_dir);
+      free (roslyn_dir);
+      for (i = 0; i < num_dlls; i++)
+        free (dlls[i]);
+      free (dlls);
+
+      return (exitstatus != 0);
+    }
+  else
+    {
+      static bool csc_tested;
+      static bool csc_present;
+
+      if (!csc_tested)
+        {
+          /* Test for presence of csc:
+             "csc -help 2>/dev/null | grep -i analyzer >/dev/null \
+              && ! { csc -help 2>/dev/null | grep -i chicken > /dev/null; }"  */
+          const char *argv[3];
+          pid_t child;
+          int fd[1];
+          int exitstatus;
+
+          argv[0] = "csc";
+          argv[1] = "-help";
+          argv[2] = NULL;
+          child = create_pipe_in ("csc", "csc", argv, NULL,
+                                  DEV_NULL, true, true, false, fd);
+          if (child != -1)
+            {
+              /* Read the subprocess output, and test whether it contains the
+                 string "analyzer" or the string "chicken".  */
+              char c[8];
+              size_t count = 0;
+              bool seen_analyzer = false;
+              bool seen_chicken = false;
+
+              csc_present = true;
+              while (safe_read (fd[0], &c[count], 1) > 0)
+                {
+                  if (c[count] >= 'A' && c[count] <= 'Z')
+                    c[count] += 'a' - 'A';
+                  count++;
+                  if (count >= 7)
+                    {
+                      if (memcmp (c, "chicken", 7) == 0)
+                        seen_chicken = true;
+                    }
+                  if (count == 8)
+                    {
+                      if (memcmp (c, "analyzer", 8) == 0)
+                        seen_analyzer = true;
+                      c[0] = c[1]; c[1] = c[2]; c[2] = c[3]; c[3] = c[4];
+                      c[4] = c[5]; c[5] = c[6]; c[6] = c[7];
+                      count--;
+                    }
+                }
+
+              close (fd[0]);
+
+              /* Remove zombie process from process list, and retrieve exit
+                 status.  */
+              exitstatus =
+                wait_subprocess (child, "csc", false, true, true, false, NULL);
+              csc_present = (exitstatus == 0 && seen_analyzer && !seen_chicken);
+            }
+          else
+            csc_present = false;
+          csc_tested = true;
+        }
+
+      if (csc_present)
+        {
+          char **malloced;
+          char **mallocedp;
+          unsigned int argc;
+          const char **argv;
+          const char **argp;
+          int exitstatus;
+          unsigned int i;
+
+          /* Here, we assume that 'csc' is a native Windows program, therefore
+             we need to use cygpath_w.  */
+          malloced =
+            (char **)
+            xmalloca ((1 + libdirs_count + sources_count * 2) * sizeof (char *));
+          mallocedp = malloced;
+
+          argc =
+            2 + 1 + 1 + libdirs_count + libraries_count
+            + (optimize ? 1 : 0) + (debug ? 1 : 0) + sources_count;
+          argv = (const char **) xmalloca ((argc + 1) * sizeof (const char *));
+
+          argp = argv;
+          *argp++ = "csc";
+          *argp++ = "-nologo";
+          *argp++ = (output_is_library ? "-target:library" : "-target:exe");
+          {
+            char *output_file_converted = cygpath_w (output_file);
+            *mallocedp++ = output_file_converted;
+            char *option = (char *) xmalloca (5 + strlen (output_file_converted) + 1);
+            memcpy (option, "-out:", 5);
+            strcpy (option + 5, output_file_converted);
+            *argp++ = option;
+          }
+          for (i = 0; i < libdirs_count; i++)
+            {
+              const char *libdir = libdirs[i];
+              char *libdir_converted = cygpath_w (libdir);
+              *mallocedp++ = libdir_converted;
+              char *option = (char *) xmalloca (5 + strlen (libdir_converted) + 1);
+              memcpy (option, "-lib:", 5);
+              strcpy (option + 5, libdir_converted);
+              *argp++ = option;
+            }
+          for (i = 0; i < libraries_count; i++)
+            {
+              char *option = (char *) xmalloca (11 + strlen (libraries[i]) + 4 + 1);
+              memcpy (option, "-reference:", 11);
+              memcpy (option + 11, libraries[i], strlen (libraries[i]));
+              strcpy (option + 11 + strlen (libraries[i]), ".dll");
+              *argp++ = option;
+            }
+          if (optimize)
+            *argp++ = "-optimize+";
+          if (debug)
+            *argp++ = "-debug+";
+          for (i = 0; i < sources_count; i++)
+            {
+              const char *source_file = sources[i];
+              char *source_file_converted = cygpath_w (source_file);
+              *mallocedp++ = source_file_converted;
+              if (strlen (source_file_converted) >= 10
+                  && memcmp (source_file_converted
+                             + strlen (source_file_converted) - 10,
+                             ".resources",
+                             10) == 0)
+                {
+                  char *option =
+                    (char *) xmalloc (10 + strlen (source_file_converted) + 1);
+                  memcpy (option, "-resource:", 10);
+                  strcpy (option + 10, source_file_converted);
+                  *mallocedp++ = option;
+                  *argp++ = option;
+                }
+              else
+                *argp++ = source_file_converted;
+            }
+          *argp = NULL;
+          /* Ensure argv length was correctly calculated.  */
+          if (argp - argv != argc)
+            abort ();
+
+          if (verbose)
+            {
+              char *command = shell_quote_argv (argv);
+              printf ("%s\n", command);
+              free (command);
+            }
+
+          exitstatus = execute ("csc", "csc", argv, NULL,
+                                false, false, false, false,
+                                true, true, NULL);
+
+          for (i = 3; i < 4 + libdirs_count + libraries_count; i++)
+            freea ((char *) argv[i]);
+          while (mallocedp > malloced)
+            free (*--mallocedp);
+          freea (argv);
+          freea (malloced);
+
+          return (exitstatus != 0);
+        }
+      else
+        return -1;
+    }
+}
+
 static int
 compile_csharp_using_sscli (const char * const *sources,
                             unsigned int sources_count,
@@ -442,6 +1035,15 @@ compile_csharp_class (const char * const *sources,
   if (result >= 0)
     return (bool) result;
 #endif
+#if CSHARP_CHOICE_DOTNET
+  result = compile_csharp_using_dotnet (sources, sources_count,
+                                        libdirs, libdirs_count,
+                                        libraries, libraries_count,
+                                        output_file, output_is_library,
+                                        optimize, debug, verbose);
+  if (result >= 0)
+    return (bool) result;
+#endif
 
   /* Then try the remaining C# implementations in our standard order.  */
 #if !CSHARP_CHOICE_MONO
@@ -454,6 +1056,16 @@ compile_csharp_class (const char * const *sources,
     return (bool) result;
 #endif
 
+#if !CSHARP_CHOICE_DOTNET
+  result = compile_csharp_using_dotnet (sources, sources_count,
+                                        libdirs, libdirs_count,
+                                        libraries, libraries_count,
+                                        output_file, output_is_library,
+                                        optimize, debug, verbose);
+  if (result >= 0)
+    return (bool) result;
+#endif
+
   result = compile_csharp_using_sscli (sources, sources_count,
                                        libdirs, libdirs_count,
                                        libraries, libraries_count,
@@ -462,6 +1074,6 @@ compile_csharp_class (const char * const *sources,
   if (result >= 0)
     return (bool) result;
 
-  error (0, 0, _("C# compiler not found, try installing mono"));
+  error (0, 0, _("C# compiler not found, try installing mono or dotnet"));
   return true;
 }
diff --git a/modules/csharpcomp b/modules/csharpcomp
index ea996fe81d..4b0a177fbb 100644
--- a/modules/csharpcomp
+++ b/modules/csharpcomp
@@ -7,15 +7,19 @@ lib/csharpcomp.c
 
 Depends-on:
 stdbool
-xmalloca
+error
+xconcat-filename
 cygpath
 execute
 spawn-pipe
 wait-process
 getline
+scandir
+alphasort
 sh-quote
 safe-read
-error
+xmalloca
+xvasprintf
 gettext-h
 memcmp
 csharpcomp-script
-- 
2.34.1

Reply via email to