From: Bruno Haible Date: Wed, 9 Oct 2024 01:20:00 +0000 (+0200) Subject: csharpcomp: Add support for dotnet. X-Git-Url: https://gitweb.git.savannah.gnu.org/gitweb/?a=commitdiff_plain;h=f5969af5a1f0d962040de91149b4c6a506207e4c;p=gnulib.git csharpcomp: Add support for dotnet. * lib/csharpcomp.c: Include , 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. --- diff --git a/ChangeLog b/ChangeLog index 539d81b601..4403490f81 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,13 @@ 2024-10-08 Bruno Haible + csharpcomp: Add support for dotnet. + * lib/csharpcomp.c: Include , 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 #include #include #include #include #include +#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