]> Savannah Git Hosting - gnulib.git/commitdiff
csharpcomp: Add support for dotnet.
authorBruno Haible <bruno@clisp.org>
Wed, 9 Oct 2024 01:20:00 +0000 (03:20 +0200)
committerBruno Haible <bruno@clisp.org>
Wed, 16 Oct 2024 12:07:36 +0000 (14:07 +0200)
* 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
lib/csharpcomp.c
modules/csharpcomp

index 539d81b601c526de3dd3c83ad6266f482b1e9ed4..4403490f81cdb3ca1933b89cdf60181da93f931f 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,13 @@
 2024-10-08  Bruno Haible  <bruno@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.
index 28247ed1a782878b00c274fa35c1119afc387eec..4caed49533949bc26ce4828ef2623406f4d65c70 100644 (file)
 /* 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)
 
 /* 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;
 }
index ea996fe81ddf8c7ed9e071eff20396282567cf36..4b0a177fbb518121ed603fcc934cccbe506d3a19 100644 (file)
@@ -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