]> Savannah Git Hosting - gnulib.git/commitdiff
javacomp-script, javacomp: Add support for javac versions > 11.
authorBruno Haible <bruno@clisp.org>
Sun, 11 Jun 2023 01:13:29 +0000 (03:13 +0200)
committerBruno Haible <bruno@clisp.org>
Sun, 11 Jun 2023 01:14:42 +0000 (03:14 +0200)
Reported by Adrian Bunk <bunk-gnu@fs.tum.de> in
<https://savannah.gnu.org/bugs/?63385>.

* m4/javacomp.m4 (gt_JAVACOMP): When the java version is > 11, use that
version as target_version, not 11. When the java version is > 17, don't
warn. Remove goodcode, failcode variables. Don't bail out if the
source-version argument or the target-version argument is in the range
12..99. Use a heuristic that works with javac versions 11..20 at least.
Update comments.
* lib/javacomp.h (compile_java_class): State that source-version and
target-version may be larger than 11.
* lib/javacomp.c (default_target_version): Accommodate versions up to
99.
(SOURCE_VERSION_BOUND): Increase to 94.
(source_version_index): Accept versions up to 99.
(get_goodcode_snippet, get_failcode_snippet): Remove functions.
(TARGET_VERSION_BOUND): Increase to 94.
(target_version_index): Accept versions up to 99.
(corresponding_classfile_version): Remove function.
(execute_and_read_line): New function, based on lib/javaversion.c.
(get_compiler_version): New function.
(is_envjavac_usable): Add parameters source_option_out,
target_option_out. Use a heuristic that works with javac versions 11..20
at least. Call cleanup_temp_dir.
(is_javac_usable): Likewise.
(compile_java_class): Update.

ChangeLog
lib/javacomp.c
lib/javacomp.h
m4/javacomp.m4

index 9f69453a99224679045b37fd29c49af983539d48..b008ba17f692215d4e6fb6f18cb4b2636833ecb7 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,32 @@
+2023-06-10  Bruno Haible  <bruno@clisp.org>
+
+       javacomp-script, javacomp: Add support for javac versions > 11.
+       Reported by Adrian Bunk <bunk-gnu@fs.tum.de> in
+       <https://savannah.gnu.org/bugs/?63385>.
+       * m4/javacomp.m4 (gt_JAVACOMP): When the java version is > 11, use that
+       version as target_version, not 11. When the java version is > 17, don't
+       warn. Remove goodcode, failcode variables. Don't bail out if the
+       source-version argument or the target-version argument is in the range
+       12..99. Use a heuristic that works with javac versions 11..20 at least.
+       Update comments.
+       * lib/javacomp.h (compile_java_class): State that source-version and
+       target-version may be larger than 11.
+       * lib/javacomp.c (default_target_version): Accommodate versions up to
+       99.
+       (SOURCE_VERSION_BOUND): Increase to 94.
+       (source_version_index): Accept versions up to 99.
+       (get_goodcode_snippet, get_failcode_snippet): Remove functions.
+       (TARGET_VERSION_BOUND): Increase to 94.
+       (target_version_index): Accept versions up to 99.
+       (corresponding_classfile_version): Remove function.
+       (execute_and_read_line): New function, based on lib/javaversion.c.
+       (get_compiler_version): New function.
+       (is_envjavac_usable): Add parameters source_option_out,
+       target_option_out. Use a heuristic that works with javac versions 11..20
+       at least. Call cleanup_temp_dir.
+       (is_javac_usable): Likewise.
+       (compile_java_class): Update.
+
 2023-06-10  Bruno Haible  <bruno@clisp.org>
 
        javacomp-script, javacomp: Remove support for javac versions < 1.6.
index 19de05d1fdbb07e57055c0864e5d1c13b595be15..d23820537f29022cb86e140d7593fab5655637d6 100644 (file)
@@ -109,19 +109,16 @@ default_target_version (void)
                 && java_version_cache[3] == '\0')
                || (java_version_cache[0] == '9'
                    && java_version_cache[1] == '\0')
-               || (java_version_cache[0] == '1'
+               || ((java_version_cache[0] >= '1'
+                    && java_version_cache[0] <= '9')
                    && (java_version_cache[1] >= '0'
-                       && java_version_cache[1] <= '1')
+                       && java_version_cache[1] <= '9')
                    && java_version_cache[2] == '\0'))
-        /* It's one of the valid target version values.  */
+        /* Here we could choose any target_version between source_version and
+           the java_version_cache.  (If it is too small, it will be incremented
+           below until it works.)  Since we documented in javacomp.h that it is
+           determined from the JVM, we do that.  */
         ;
-      else if (java_version_cache[0] == '1'
-               && (java_version_cache[1] >= '2'
-                   && java_version_cache[1] <= '7')
-               && java_version_cache[2] == '\0')
-        /* Assume that these (not yet released) Java versions will behave
-           like the preceding ones.  */
-        java_version_cache = "11";
       else
         java_version_cache = "1.6";
     }
@@ -131,7 +128,7 @@ default_target_version (void)
 /* ======================= Source version dependent ======================= */
 
 /* Convert a source version to an index.  */
-#define SOURCE_VERSION_BOUND 6 /* exclusive upper bound */
+#define SOURCE_VERSION_BOUND 94 /* exclusive upper bound */
 static unsigned int
 source_version_index (const char *source_version)
 {
@@ -143,60 +140,18 @@ source_version_index (const char *source_version)
     }
   else if (source_version[0] == '9' && source_version[1] == '\0')
     return 3;
-  else if (source_version[0] == '1'
-           && (source_version[1] >= '0' && source_version[1] <= '1')
+  else if ((source_version[0] >= '1' && source_version[0] <= '9')
+           && (source_version[1] >= '0' && source_version[1] <= '9')
            && source_version[2] == '\0')
-    return source_version[1] - '0' + 4;
+    return (source_version[0] - '1') * 10 + source_version[1] - '0' + 4;
   error (EXIT_FAILURE, 0, _("invalid source_version argument to compile_java_class"));
   return 0;
 }
 
-/* Return a snippet of code that should compile in the given source version.  */
-static const char *
-get_goodcode_snippet (const char *source_version)
-{
-  if (strcmp (source_version, "1.6") == 0)
-    return "class conftest<T> { T foo() { return null; } }\n";
-  if (strcmp (source_version, "1.7") == 0)
-    return "class conftest { void foo () { switch (\"A\") {} } }\n";
-  if (strcmp (source_version, "1.8") == 0)
-    return "class conftest { void foo () { Runnable r = () -> {}; } }\n";
-  if (strcmp (source_version, "9") == 0)
-    return "interface conftest { private void foo () {} }\n";
-  if (strcmp (source_version, "10") == 0)
-    return "class conftest { public void m() { var i = new Integer(0); } }\n";
-  if (strcmp (source_version, "11") == 0)
-    return "class conftest { Readable r = (var b) -> 0; }\n";
-  error (EXIT_FAILURE, 0, _("invalid source_version argument to compile_java_class"));
-  return NULL;
-}
-
-/* Return a snippet of code that should fail to compile in the given source
-   version, or NULL (standing for a snippet that would fail to compile with
-   any compiler).  */
-static const char *
-get_failcode_snippet (const char *source_version)
-{
-  if (strcmp (source_version, "1.6") == 0)
-    return "class conftestfail { void foo () { switch (\"A\") {} } }\n";
-  if (strcmp (source_version, "1.7") == 0)
-    return "class conftestfail { void foo () { Runnable r = () -> {}; } }\n";
-  if (strcmp (source_version, "1.8") == 0)
-    return "interface conftestfail { private void foo () {} }\n";
-  if (strcmp (source_version, "9") == 0)
-    return "class conftestfail { public void m() { var i = new Integer(0); } }\n";
-  if (strcmp (source_version, "10") == 0)
-    return "class conftestfail { Readable r = (var b) -> 0; }\n";
-  if (strcmp (source_version, "11") == 0)
-    return NULL;
-  error (EXIT_FAILURE, 0, _("invalid source_version argument to compile_java_class"));
-  return NULL;
-}
-
 /* ======================= Target version dependent ======================= */
 
 /* Convert a target version to an index.  */
-#define TARGET_VERSION_BOUND 6 /* exclusive upper bound */
+#define TARGET_VERSION_BOUND 94 /* exclusive upper bound */
 static unsigned int
 target_version_index (const char *target_version)
 {
@@ -206,31 +161,10 @@ target_version_index (const char *target_version)
     return target_version[2] - '6';
   else if (target_version[0] == '9' && target_version[1] == '\0')
     return 3;
-  else if (target_version[0] == '1'
-           && (target_version[1] >= '0' && target_version[1] <= '1')
+  else if ((target_version[0] >= '1' && target_version[0] <= '9')
+           && (target_version[1] >= '0' && target_version[1] <= '9')
            && target_version[2] == '\0')
-    return target_version[1] - '0' + 4;
-  error (EXIT_FAILURE, 0, _("invalid target_version argument to compile_java_class"));
-  return 0;
-}
-
-/* Return the class file version number corresponding to a given target
-   version.  */
-static int
-corresponding_classfile_version (const char *target_version)
-{
-  if (strcmp (target_version, "1.6") == 0)
-    return 50;
-  if (strcmp (target_version, "1.7") == 0)
-    return 51;
-  if (strcmp (target_version, "1.8") == 0)
-    return 52;
-  if (strcmp (target_version, "9") == 0)
-    return 53;
-  if (strcmp (target_version, "10") == 0)
-    return 54;
-  if (strcmp (target_version, "11") == 0)
-    return 55;
+    return (target_version[0] - '1') * 10 + target_version[1] - '0' + 4;
   error (EXIT_FAILURE, 0, _("invalid target_version argument to compile_java_class"));
   return 0;
 }
@@ -389,6 +323,121 @@ compile_using_javac (const char * const *java_sources,
 
 /* ====================== Usability test subroutines ====================== */
 
+/* Executes a program.
+   Returns the first line of its output, as a freshly allocated string, or
+   NULL.  */
+static char *
+execute_and_read_line (const char *progname,
+                       const char *prog_path, const char * const *prog_argv)
+{
+  pid_t child;
+  int fd[1];
+  FILE *fp;
+  char *line;
+  size_t linesize;
+  size_t linelen;
+  int exitstatus;
+
+  /* Open a pipe to the program.  */
+  child = create_pipe_in (progname, prog_path, prog_argv, NULL,
+                          DEV_NULL, false, true, false, fd);
+
+  if (child == -1)
+    return NULL;
+
+  /* Retrieve its result.  */
+  fp = fdopen (fd[0], "r");
+  if (fp == NULL)
+    {
+      error (0, errno, _("fdopen() failed"));
+      return NULL;
+    }
+
+  line = NULL; linesize = 0;
+  linelen = getline (&line, &linesize, fp);
+  if (linelen == (size_t)(-1))
+    {
+      error (0, 0, _("%s subprocess I/O error"), progname);
+      return NULL;
+    }
+  if (linelen > 0 && line[linelen - 1] == '\n')
+    line[linelen - 1] = '\0';
+
+  /* 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.  */
+  exitstatus =
+    wait_subprocess (child, progname, true, false, true, false, NULL);
+  if (exitstatus != 0)
+    {
+      free (line);
+      return NULL;
+    }
+
+  return line;
+}
+
+/* Executes a program, assumed to be a Java compiler with '-version' option.
+   Returns the version number.  */
+static unsigned int
+get_compiler_version (const char *progname,
+                      const char *prog_path, const char * const *prog_argv)
+{
+  char *line = execute_and_read_line (progname, prog_path, prog_argv);
+  if (line == NULL)
+    return 0;
+
+  /* Search the first digit in line.  */
+  char *version_start = line;
+  for (version_start = line; ; version_start++)
+    {
+      if (*version_start == '\0')
+        {
+          /* No digits found.  */
+          free (line);
+          return 0;
+        }
+      if (*version_start >= '0' && *version_start <= '9')
+        break;
+    }
+
+  /* Search the end of the version string.  */
+  char *version_end = version_start;
+  while ((*version_end >= '0' && *version_end <= '9') || *version_end == '.')
+    version_end++;
+  *version_end = '\0';
+
+  /* Map 1.6.0_85 to 6, 1.8.0_151 to 8.  Map 9.0.4 to 9, 10.0.2 to 10, etc.  */
+  if (version_start[0] == '1' && version_start[1] == '.')
+    version_start += 2;
+  version_end = strchr (version_start, '.');
+  if (version_end != NULL)
+    *version_end = '\0';
+
+  /* Convert number to 'unsigned int'.  */
+  unsigned int result;
+  switch (strlen (version_start))
+    {
+    case 1:
+      result = version_start[0] - '0';
+      break;
+
+    case 2:
+      result = (version_start[0] - '0') * 10 + (version_start[1] - '0');
+      break;
+
+    default:
+      result = 0;
+    }
+
+  free (line);
+  return result;
+}
+
 /* Write a given contents to a temporary file.
    FILE_NAME is the name of a file inside TMPDIR that is known not to exist
    yet.
@@ -452,15 +501,15 @@ static bool
 is_envjavac_usable (const char *javac,
                     const char *source_version, const char *target_version,
                     bool *usablep,
-                    bool *source_option_p, bool *target_option_p)
+                    char source_option_out[30], char target_option_out[30])
 {
   /* The cache depends on the source_version and target_version.  */
   struct result_t
   {
-    bool tested;
-    bool usable;
-    bool source_option;
-    bool target_option;
+    /*bool*/ unsigned int tested : 1;
+    /*bool*/ unsigned int usable : 1;
+    unsigned int source_option : 7;
+    unsigned int target_option : 7;
   };
   static struct result_t result_cache[SOURCE_VERSION_BOUND][TARGET_VERSION_BOUND];
   struct result_t *resultp;
@@ -469,144 +518,210 @@ is_envjavac_usable (const char *javac,
                          [target_version_index (target_version)];
   if (!resultp->tested)
     {
-      /* Try $JAVAC.  */
-      struct temp_dir *tmpdir;
-      char *conftest_file_name;
-      char *compiled_file_name;
-      const char *java_sources[1];
-      struct stat statbuf;
-
-      tmpdir = create_temp_dir ("java", NULL, false);
-      if (tmpdir == NULL)
-        return true;
-
-      conftest_file_name =
-        xconcatenated_filename (tmpdir->dir_name, "conftest.java", NULL);
-      if (write_temp_file (tmpdir, conftest_file_name,
-                           get_goodcode_snippet (source_version)))
-        {
-          free (conftest_file_name);
-          cleanup_temp_dir (tmpdir);
-          return true;
-        }
-
-      compiled_file_name =
-        xconcatenated_filename (tmpdir->dir_name, "conftest.class", NULL);
-      register_temp_file (tmpdir, compiled_file_name);
-
-      java_sources[0] = conftest_file_name;
-      if (!compile_using_envjavac (javac,
-                                   java_sources, 1, tmpdir->dir_name,
-                                   false, false, false, true)
-          && stat (compiled_file_name, &statbuf) >= 0
-          && get_classfile_version (compiled_file_name)
-             <= corresponding_classfile_version (target_version))
+      /* Canonicalize source_version and target_version, for easier
+         arithmetic.  */
+      int try_source_version = 6 + source_version_index (source_version);
+      int try_target_version = 6 + target_version_index (target_version);
+      /* Sanity check.  */
+      if (try_source_version <= try_target_version)
         {
-          /* $JAVAC compiled conftest.java successfully.  */
-          /* Try adding -source option if it is useful.  */
-          char *javac_source =
-            xasprintf ("%s -source %s", javac, source_version);
-          assume (javac_source != NULL);
+          /* Try $JAVAC.  */
+          struct temp_dir *tmpdir;
+          char *conftest_file_name;
+          char *compiled_file_name;
+          const char *java_sources[1];
+          struct stat statbuf;
+
+          tmpdir = create_temp_dir ("java", NULL, false);
+          if (tmpdir == NULL)
+            return true;
+
+          conftest_file_name =
+            xconcatenated_filename (tmpdir->dir_name, "conftest.java", NULL);
+          if (write_temp_file (tmpdir, conftest_file_name, "class conftest {}"))
+            {
+              free (conftest_file_name);
+              cleanup_temp_dir (tmpdir);
+              return true;
+            }
 
-          unlink (compiled_file_name);
+          compiled_file_name =
+            xconcatenated_filename (tmpdir->dir_name, "conftest.class", NULL);
+          register_temp_file (tmpdir, compiled_file_name);
 
           java_sources[0] = conftest_file_name;
-          if (!compile_using_envjavac (javac_source,
+          if (!compile_using_envjavac (javac,
                                        java_sources, 1, tmpdir->dir_name,
                                        false, false, false, true)
-              && stat (compiled_file_name, &statbuf) >= 0
-              && get_classfile_version (compiled_file_name)
-                 <= corresponding_classfile_version (target_version))
+              && stat (compiled_file_name, &statbuf) >= 0)
             {
-              const char *failcode = get_failcode_snippet (source_version);
-
-              if (failcode != NULL)
+              /* $JAVAC compiled conftest.java successfully.  */
+              int compiler_cfversion =
+                get_classfile_version (compiled_file_name);
+              int compiler_target_version = compiler_cfversion - 44;
+
+              /* It is hard to determine the compiler_source_version.  This
+                 would require a list of code snippets that can be compiled only
+                 with a specific '-source' option and up, and this list would
+                 need to grow every 6 months.
+                 Also, $JAVAC may already include a '-source' option.
+                 Therefore, pass a '-source' option always.  */
+              char source_option[30];
+              sprintf (source_option, " -source %s%d",
+                       try_source_version <= 8 ? "1." : "",
+                       try_source_version);
+
+              /* And pass a '-target' option as well, if needed.
+                 (All supported javac versions support both, see the table in
+                 javacomp.m4.)  */
+              char target_option[30];
+              if (try_target_version == compiler_target_version)
+                target_option[0] = '\0';
+              else
+                sprintf (target_option, " -target %s%d",
+                         try_target_version <= 8 ? "1." : "",
+                         try_target_version);
+
+              char *javac_source_target =
+                xasprintf ("%s%s%s", javac, source_option, target_option);
+              assume (javac_source_target != NULL);
+
+              unlink (compiled_file_name);
+
+              java_sources[0] = conftest_file_name;
+              if (!compile_using_envjavac (javac_source_target,
+                                           java_sources, 1, tmpdir->dir_name,
+                                           false, false, false, true)
+                  && stat (compiled_file_name, &statbuf) >= 0)
                 {
-                  free (compiled_file_name);
-                  free (conftest_file_name);
-
-                  conftest_file_name =
-                    xconcatenated_filename (tmpdir->dir_name,
-                                            "conftestfail.java",
-                                            NULL);
-                  if (write_temp_file (tmpdir, conftest_file_name, failcode))
+                  /* The compiler directly supports the desired source_version
+                     and target_version.  Perfect.  */
+                  free (javac_source_target);
+
+                  resultp->source_option = try_source_version;
+                  resultp->target_option =
+                    (try_target_version == compiler_target_version ? 0 :
+                     try_target_version);
+                  resultp->usable = true;
+                }
+              else
+                {
+                  /* If the desired source_version or target_version were too
+                     large for the compiler, there's nothing else we can do.  */
+                  unsigned int compiler_version;
+
+                  free (javac_source_target);
+
+                  {
+                    size_t command_length;
+                    char *command;
+                    const char *argv[4];
+
+                    command_length = strlen (javac) + 9 + 1;
+
+                    command = (char *) xmalloca (command_length);
                     {
-                      free (conftest_file_name);
-                      free (javac_source);
-                      cleanup_temp_dir (tmpdir);
-                      return true;
+                      char *p = command;
+                      p = stpcpy (p, javac);
+                      p = stpcpy (p, " -version");
+                      *p++ = '\0';
+                      /* Ensure command_length was correctly calculated.  */
+                      if (p - command > command_length)
+                        abort ();
                     }
 
-                  compiled_file_name =
-                    xconcatenated_filename (tmpdir->dir_name,
-                                            "conftestfail.class",
-                                            NULL);
-                  register_temp_file (tmpdir, compiled_file_name);
-
-                  java_sources[0] = conftest_file_name;
-                  if (!compile_using_envjavac (javac,
-                                               java_sources, 1,
-                                               tmpdir->dir_name,
-                                               false, false, false, true)
-                      && stat (compiled_file_name, &statbuf) >= 0)
+                    argv[0] = BOURNE_SHELL;
+                    argv[1] = "-c";
+                    argv[2] = command;
+                    argv[3] = NULL;
+                    compiler_version =
+                      get_compiler_version (javac, BOURNE_SHELL, argv);
+
+                    freea (command);
+                  }
+
+                  if (try_source_version <= compiler_version
+                      && try_target_version <= compiler_version)
                     {
-                      unlink (compiled_file_name);
-
-                      java_sources[0] = conftest_file_name;
-                      if (compile_using_envjavac (javac_source,
-                                                  java_sources, 1,
-                                                  tmpdir->dir_name,
-                                                  false, false, false, true))
-                        /* $JAVAC compiled conftestfail.java successfully, and
-                           "$JAVAC -source $source_version" rejects it.
-                           So the -source option is useful.  */
-                        resultp->source_option = true;
+                      /* Increase try_source_version and compiler_version until
+                         the compiler accepts these values.  This is necessary
+                         to make e.g. try_source_version = 6 work with Java 12
+                         or newer, or try_source_version = 7 work with Java 20
+                         or newer.  */
+                      for (;;)
+                        {
+                          /* Invariant:
+                             try_source_version <= try_target_version.  */
+                          if (try_source_version == try_target_version)
+                            try_target_version++;
+                          try_source_version++;
+                          if (try_source_version > compiler_version)
+                            break;
+
+                          sprintf (source_option, " -source %s%d",
+                                   try_source_version <= 8 ? "1." : "",
+                                   try_source_version);
+
+                          if (try_target_version == compiler_target_version)
+                            target_option[0] = '\0';
+                          else
+                            sprintf (target_option, " -target %s%d",
+                                     try_target_version <= 8 ? "1." : "",
+                                     try_target_version);
+
+                          javac_source_target =
+                            xasprintf ("%s%s%s", javac,
+                                       source_option, target_option);
+                          assume (javac_source_target != NULL);
+
+                          unlink (compiled_file_name);
+
+                          java_sources[0] = conftest_file_name;
+                          if (!compile_using_envjavac (javac_source_target,
+                                                       java_sources, 1,
+                                                       tmpdir->dir_name,
+                                                       false, false,
+                                                       false, true)
+                              && stat (compiled_file_name, &statbuf) >= 0)
+                            {
+                              /* The compiler supports the try_source_version
+                                 and try_target_version.  It's better than
+                                 nothing.  */
+                              free (javac_source_target);
+
+                              resultp->source_option = try_source_version;
+                              resultp->target_option =
+                                (try_target_version == compiler_target_version ? 0 :
+                                 try_target_version);
+                              resultp->usable = true;
+                              break;
+                            }
+
+                          free (javac_source_target);
+                        }
                     }
                 }
             }
 
-          free (javac_source);
-
-          resultp->usable = true;
-        }
-      else
-        {
-          /* Try with -target and -source options. (All supported javac
-             versions support both, see the table in javacomp.m4.)  */
-          char *javac_target_source =
-            xasprintf ("%s -target %s -source %s", javac,
-                       target_version, source_version);
-          assume (javac_target_source != NULL);
-
-          unlink (compiled_file_name);
-
-          java_sources[0] = conftest_file_name;
-          if (!compile_using_envjavac (javac_target_source,
-                                       java_sources, 1, tmpdir->dir_name,
-                                       false, false, false, true)
-              && stat (compiled_file_name, &statbuf) >= 0
-              && get_classfile_version (compiled_file_name)
-                 <= corresponding_classfile_version (target_version))
-            {
-              /* "$JAVAC -target $target_version -source $source_version"
-                 compiled conftest.java successfully.  */
-              resultp->source_option = true;
-              resultp->target_option = true;
-              resultp->usable = true;
-            }
+          cleanup_temp_dir (tmpdir);
 
-          free (javac_target_source);
+          free (compiled_file_name);
+          free (conftest_file_name);
         }
 
-      free (compiled_file_name);
-      free (conftest_file_name);
-
       resultp->tested = true;
     }
 
   *usablep = resultp->usable;
-  *source_option_p = resultp->source_option;
-  *target_option_p = resultp->target_option;
+  sprintf (source_option_out, " -source %s%d",
+           resultp->source_option <= 8 ? "1." : "",
+           resultp->source_option);
+  if (resultp->target_option == 0)
+    target_option_out[0] = '\0';
+  else
+    sprintf (target_option_out, " -target %s%d",
+             resultp->target_option <= 8 ? "1." : "",
+             resultp->target_option);
   return false;
 }
 
@@ -639,15 +754,16 @@ is_javac_present (void)
    Return a failure indicator (true upon error).  */
 static bool
 is_javac_usable (const char *source_version, const char *target_version,
-                 bool *usablep, bool *source_option_p, bool *target_option_p)
+                 bool *usablep,
+                 char source_option_out[20], char target_option_out[20])
 {
   /* The cache depends on the source_version and target_version.  */
   struct result_t
   {
-    bool tested;
-    bool usable;
-    bool source_option;
-    bool target_option;
+    /*bool*/ unsigned int tested : 1;
+    /*bool*/ unsigned int usable : 1;
+    unsigned int source_option : 7;
+    unsigned int target_option : 7;
   };
   static struct result_t result_cache[SOURCE_VERSION_BOUND][TARGET_VERSION_BOUND];
   struct result_t *resultp;
@@ -656,136 +772,178 @@ is_javac_usable (const char *source_version, const char *target_version,
                          [target_version_index (target_version)];
   if (!resultp->tested)
     {
-      /* Try javac.  */
-      struct temp_dir *tmpdir;
-      char *conftest_file_name;
-      char *compiled_file_name;
-      const char *java_sources[1];
-      struct stat statbuf;
-
-      tmpdir = create_temp_dir ("java", NULL, false);
-      if (tmpdir == NULL)
-        return true;
-
-      conftest_file_name =
-        xconcatenated_filename (tmpdir->dir_name, "conftest.java", NULL);
-      if (write_temp_file (tmpdir, conftest_file_name,
-                           get_goodcode_snippet (source_version)))
+      /* Canonicalize source_version and target_version, for easier
+         arithmetic.  */
+      int try_source_version = 6 + source_version_index (source_version);
+      int try_target_version = 6 + target_version_index (target_version);
+      /* Sanity check.  */
+      if (try_source_version <= try_target_version)
         {
-          free (conftest_file_name);
-          cleanup_temp_dir (tmpdir);
-          return true;
-        }
+          /* Try javac.  */
+          struct temp_dir *tmpdir;
+          char *conftest_file_name;
+          char *compiled_file_name;
+          const char *java_sources[1];
+          struct stat statbuf;
+
+          tmpdir = create_temp_dir ("java", NULL, false);
+          if (tmpdir == NULL)
+            return true;
+
+          conftest_file_name =
+            xconcatenated_filename (tmpdir->dir_name, "conftest.java", NULL);
+          if (write_temp_file (tmpdir, conftest_file_name, "class conftest {}"))
+            {
+              free (conftest_file_name);
+              cleanup_temp_dir (tmpdir);
+              return true;
+            }
 
-      compiled_file_name =
-        xconcatenated_filename (tmpdir->dir_name, "conftest.class", NULL);
-      register_temp_file (tmpdir, compiled_file_name);
-
-      java_sources[0] = conftest_file_name;
-      if (!compile_using_javac (java_sources, 1,
-                                false, source_version,
-                                false, target_version,
-                                tmpdir->dir_name, false, false, false, true)
-          && stat (compiled_file_name, &statbuf) >= 0
-          && get_classfile_version (compiled_file_name)
-             <= corresponding_classfile_version (target_version))
-        {
-          /* javac compiled conftest.java successfully.  */
-          /* Try adding -source option if it is useful.  */
-          unlink (compiled_file_name);
+          compiled_file_name =
+            xconcatenated_filename (tmpdir->dir_name, "conftest.class", NULL);
+          register_temp_file (tmpdir, compiled_file_name);
 
           java_sources[0] = conftest_file_name;
           if (!compile_using_javac (java_sources, 1,
-                                    true, source_version,
+                                    false, source_version,
                                     false, target_version,
-                                    tmpdir->dir_name, false, false, false, true)
-              && stat (compiled_file_name, &statbuf) >= 0
-              && get_classfile_version (compiled_file_name)
-                 <= corresponding_classfile_version (target_version))
+                                    tmpdir->dir_name,
+                                    false, false, false, true)
+              && stat (compiled_file_name, &statbuf) >= 0)
             {
-              const char *failcode = get_failcode_snippet (source_version);
-
-              if (failcode != NULL)
+              /* javac compiled conftest.java successfully.  */
+              int compiler_cfversion =
+                get_classfile_version (compiled_file_name);
+              int compiler_target_version = compiler_cfversion - 44;
+
+              /* It is hard to determine the compiler_source_version.  This
+                 would require a list of code snippets that can be compiled only
+                 with a specific '-source' option and up, and this list would
+                 need to grow every 6 months.
+                 Also, javac may point to a shell script that already includes a
+                 '-source' option.
+                 Therefore, pass a '-source' option always.  */
+              char source_option[20];
+              sprintf (source_option, "%s%d",
+                       try_source_version <= 8 ? "1." : "",
+                       try_source_version);
+
+              /* And pass a '-target' option as well, if needed.
+                 (All supported javac versions support both, see the table in
+                 javacomp.m4.)  */
+              char target_option[20];
+              sprintf (target_option, "%s%d",
+                       try_target_version <= 8 ? "1." : "",
+                       try_target_version);
+
+              unlink (compiled_file_name);
+
+              java_sources[0] = conftest_file_name;
+              if (!compile_using_javac (java_sources, 1,
+                                        true,
+                                        source_option,
+                                        try_target_version != compiler_target_version,
+                                        target_option,
+                                        tmpdir->dir_name,
+                                        false, false, false, true)
+                  && stat (compiled_file_name, &statbuf) >= 0)
                 {
-                  free (compiled_file_name);
-                  free (conftest_file_name);
-
-                  conftest_file_name =
-                    xconcatenated_filename (tmpdir->dir_name,
-                                            "conftestfail.java",
-                                            NULL);
-                  if (write_temp_file (tmpdir, conftest_file_name, failcode))
-                    {
-                      free (conftest_file_name);
-                      cleanup_temp_dir (tmpdir);
-                      return true;
-                    }
-
-                  compiled_file_name =
-                    xconcatenated_filename (tmpdir->dir_name,
-                                            "conftestfail.class",
-                                            NULL);
-                  register_temp_file (tmpdir, compiled_file_name);
-
-                  java_sources[0] = conftest_file_name;
-                  if (!compile_using_javac (java_sources, 1,
-                                            false, source_version,
-                                            false, target_version,
-                                            tmpdir->dir_name,
-                                            false, false, false, true)
-                      && stat (compiled_file_name, &statbuf) >= 0)
+                  /* The compiler directly supports the desired source_version
+                     and target_version.  Perfect.  */
+                  resultp->source_option = try_source_version;
+                  resultp->target_option =
+                    (try_target_version == compiler_target_version ? 0 :
+                     try_target_version);
+                  resultp->usable = true;
+                }
+              else
+                {
+                  /* If the desired source_version or target_version were too
+                     large for the compiler, there's nothing else we can do.  */
+                  unsigned int compiler_version;
+
+                  {
+                    const char *argv[3];
+
+                    argv[0] = "javac";
+                    argv[1] = "-version";
+                    argv[2] = NULL;
+                    compiler_version =
+                      get_compiler_version ("javac", argv[0], argv);
+                  }
+
+                  if (try_source_version <= compiler_version
+                      && try_target_version <= compiler_version)
                     {
-                      unlink (compiled_file_name);
-
-                      java_sources[0] = conftest_file_name;
-                      if (compile_using_javac (java_sources, 1,
-                                               true, source_version,
-                                               false, target_version,
-                                               tmpdir->dir_name,
-                                               false, false, false, true))
-                        /* javac compiled conftestfail.java successfully, and
-                           "javac -source $source_version" rejects it.
-                           So the -source option is useful.  */
-                        resultp->source_option = true;
+                      /* Increase try_source_version and compiler_version until
+                         the compiler accepts these values.  This is necessary
+                         to make e.g. try_source_version = 6 work with Java 12
+                         or newer, or try_source_version = 7 work with Java 20
+                         or newer.  */
+                      for (;;)
+                        {
+                          /* Invariant:
+                             try_source_version <= try_target_version.  */
+                          if (try_source_version == try_target_version)
+                            try_target_version++;
+                          try_source_version++;
+                          if (try_source_version > compiler_version)
+                            break;
+
+                          sprintf (source_option, "%s%d",
+                                   try_source_version <= 8 ? "1." : "",
+                                   try_source_version);
+
+                          sprintf (target_option, "%s%d",
+                                   try_target_version <= 8 ? "1." : "",
+                                   try_target_version);
+
+                          unlink (compiled_file_name);
+
+                          java_sources[0] = conftest_file_name;
+                          if (!compile_using_javac (java_sources, 1,
+                                                    true,
+                                                    source_option,
+                                                    try_target_version != compiler_target_version,
+                                                    target_option,
+                                                    tmpdir->dir_name,
+                                                    false, false, false, true)
+                              && stat (compiled_file_name, &statbuf) >= 0)
+                            {
+                              /* The compiler supports the try_source_version
+                                 and try_target_version.  It's better than
+                                 nothing.  */
+                              resultp->source_option = try_source_version;
+                              resultp->target_option =
+                                (try_target_version == compiler_target_version ? 0 :
+                                 try_target_version);
+                              resultp->usable = true;
+                              break;
+                            }
+                        }
                     }
                 }
             }
 
-          resultp->usable = true;
-        }
-      else
-        {
-          /* Try with -target and -source options. (All supported javac
-             versions support both, see the table in javacomp.m4.)  */
-          unlink (compiled_file_name);
+          cleanup_temp_dir (tmpdir);
 
-          java_sources[0] = conftest_file_name;
-          if (!compile_using_javac (java_sources, 1,
-                                    true, source_version,
-                                    true, target_version,
-                                    tmpdir->dir_name,
-                                    false, false, false, true)
-              && stat (compiled_file_name, &statbuf) >= 0
-              && get_classfile_version (compiled_file_name)
-                 <= corresponding_classfile_version (target_version))
-            {
-              /* "javac -target $target_version -source $source_version"
-                 compiled conftest.java successfully.  */
-              resultp->source_option = true;
-              resultp->target_option = true;
-              resultp->usable = true;
-            }
+          free (compiled_file_name);
+          free (conftest_file_name);
         }
 
-      free (compiled_file_name);
-      free (conftest_file_name);
-
       resultp->tested = true;
     }
 
   *usablep = resultp->usable;
-  *source_option_p = resultp->source_option;
-  *target_option_p = resultp->target_option;
+  sprintf (source_option_out, "%s%d",
+           resultp->source_option <= 8 ? "1." : "",
+           resultp->source_option);
+  if (resultp->target_option == 0)
+    target_option_out[0] = '\0';
+  else
+    sprintf (target_option_out, "%s%d",
+             resultp->target_option <= 8 ? "1." : "",
+             resultp->target_option);
   return false;
 }
 
@@ -824,8 +982,8 @@ compile_java_class (const char * const *java_sources,
     if (javac != NULL && javac[0] != '\0')
       {
         bool usable = false;
-        bool source_option = false;
-        bool target_option = false;
+        char source_option[30];
+        char target_option[30];
 
         if (target_version == NULL)
           target_version = default_target_version ();
@@ -833,7 +991,7 @@ compile_java_class (const char * const *java_sources,
         if (is_envjavac_usable (javac,
                                 source_version, target_version,
                                 &usable,
-                                &source_option, &target_option))
+                                source_option, target_option))
           {
             err = true;
             goto done1;
@@ -849,12 +1007,7 @@ compile_java_class (const char * const *java_sources,
               set_classpath (classpaths, classpaths_count, false, verbose);
 
             javac_with_options =
-              xasprintf ("%s%s%s%s%s",
-                         javac,
-                         source_option ? " -source " : "",
-                         source_option ? source_version : "",
-                         target_option ? " -target " : "",
-                         target_option ? target_version : "");
+              xasprintf ("%s%s%s", javac, source_option, target_option);
             assume (javac_with_options != NULL);
 
             err = compile_using_envjavac (javac_with_options,
@@ -883,14 +1036,15 @@ compile_java_class (const char * const *java_sources,
   if (is_javac_present ())
     {
       bool usable = false;
-      bool source_option = false;
-      bool target_option = false;
+      char source_option[20];
+      char target_option[20];
 
       if (target_version == NULL)
         target_version = default_target_version ();
 
       if (is_javac_usable (source_version, target_version,
-                           &usable, &source_option, &target_option))
+                           &usable,
+                           source_option, target_option))
         {
           err = true;
           goto done1;
@@ -909,8 +1063,8 @@ compile_java_class (const char * const *java_sources,
                            verbose);
 
           err = compile_using_javac (java_sources, java_sources_count,
-                                     source_option, source_version,
-                                     target_option, target_version,
+                                     true, source_option,
+                                     target_option[0] != '\0', target_option,
                                      directory, optimize, debug, verbose,
                                      false);
 
index e6e5d622e60cd8acabc049bc079b8b49d51ec78d..2c1ae811a4d02143881b7325236112f23f889346 100644 (file)
@@ -29,6 +29,7 @@
              9               private interface methods
             10               type inference for local variables
             11               'var' in parameters of lambda expressions
+            ...
    If source-version 1.3 or 1.4 or 1.5 is requested, it gets mapped to 1.6, for
    backward compatibility. (Currently the minimum Java and javac version we need
    to support is Java 1.6, since that's the default Java version on Solaris 10.)
@@ -40,6 +41,7 @@
              9                   53.0
             10                   54.0
             11                   55.0
+            ...                  ...
    If a target-version below 1.6 is requested, it gets mapped to 1.6, for
    backward compatibility. (Currently the minimum Java and javac version we need
    to support is Java 1.6, since that's the default Java version on Solaris 10.)
index e20b309f3a450af4fc1b07ec7d5e3c1ef99aed8e..009c788cb6393b76edb2f0730fd1781bbf98ea0b 100644 (file)
@@ -1,4 +1,4 @@
-# javacomp.m4 serial 24
+# javacomp.m4 serial 25
 dnl Copyright (C) 2001-2003, 2006-2007, 2009-2023 Free Software Foundation,
 dnl Inc.
 dnl This file is free software; the Free Software Foundation
@@ -18,6 +18,8 @@ dnl with or without modifications, as long as this notice is preserved.
 #           9               private interface methods
 #          10               type inference for local variables
 #          11               'var' in parameters of lambda expressions
+#          ...
+# (For reference, see <https://en.wikipedia.org/wiki/Java_version_history>.)
 # If source-version 1.3 or 1.4 or 1.5 is requested, it gets mapped to 1.6, for
 # backward compatibility. (Currently the minimum Java and javac version we need
 # to support is Java 1.6, since that's the default Java version on Solaris 10.)
@@ -29,6 +31,7 @@ dnl with or without modifications, as long as this notice is preserved.
 #           9                   53.0
 #          10                   54.0
 #          11                   55.0
+#          ...                  ...
 # The classfile version of a .class file can be determined through the "file"
 # command. More portably, the classfile major version can be determined through
 # "od -A n -t d1 -j 7 -N 1 classfile".
@@ -45,6 +48,7 @@ dnl with or without modifications, as long as this notice is preserved.
 #           9           JDK/JRE 9
 #          10           JDK/JRE 10
 #          11           JDK/JRE 11
+#          ...          ...
 #
 # Specifying target-version is useful when building a library (.jar) that is
 # useful outside the given package. Omitting target-version is useful when
@@ -106,25 +110,28 @@ changequote([,])dnl
          | tr -d '\012\015' \
          | tr '!"#$%&()*+,./0123456789:;<=>?@ABCDEFGHJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzI' '\000\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037\040\041\046\050\051\052\056\057\073\074\076\103\106\114\116\117\120\123\124\126\133\141\142\143\144\145\146\147\151\152\154\155\156\157\160\162\163\164\165\166\171\261\262\266\267\270\272\276\312\376\055' \
          > conftestver.class
-       target_version=`{
+       java_exec_version=`{
          unset JAVA_HOME
          echo "$as_me:__oline__: CLASSPATH=.${CLASSPATH:+$CLASSPATH_SEPARATOR$CLASSPATH} $CONF_JAVA conftestver" >&AS_MESSAGE_LOG_FD
          CLASSPATH=.${CLASSPATH:+$CLASSPATH_SEPARATOR$CLASSPATH} $CONF_JAVA conftestver 2>&AS_MESSAGE_LOG_FD
        }`
-       case "$target_version" in
+       case "$java_exec_version" in
          null)
            dnl JDK 1.1.X returns null.
-           target_version=1.1 ;;
+           java_exec_version=1.1 ;;
        esac
-       case "$target_version" in
+       case "$java_exec_version" in
          1.1 | 1.2 | 1.3 | 1.4 | 1.5)
            AC_MSG_WARN([$CONF_JAVA is too old, cannot compile Java code for this old version any more])
            target_version=1.6 ;;
-         1.6 | 1.7 | 1.8 | 9 | 10 | 11) ;;
-         12 | 13 | 14 | 15 | 16 | 17)
-           dnl Assume that these (not yet released) Java versions will behave
-           dnl like the preceding ones.
-           target_version=11 ;;
+changequote(,)dnl
+         1.6 | 1.7 | 1.8 | 9 | [1-9][0-9])
+changequote([,])dnl
+           dnl Here we could choose any target_version between $source_version
+           dnl and the $java_exec_version. (If it is too small, it will be
+           dnl incremented below until it works.) Since we documented above that
+           dnl it is determined from the JVM, we do that:
+           target_version="$java_exec_version" ;;
          *) AC_MSG_WARN([unknown target-version $target_version, please update gt_@&t@JAVACOMP macro])
             target_version=1.6 ;;
        esac
@@ -138,27 +145,15 @@ changequote([,])dnl
      esac
     ])
   case "$source_version" in
-    1.6) goodcode='class conftest<T>     { T foo() { return null; } }'
-         failcode='class conftestfail { void foo () { switch ("A") {} } }' ;;
-    1.7) goodcode='class conftest     { void foo () { switch ("A") {} } }'
-         failcode='class conftestfail { void foo () { Runnable r = () -> {}; } }' ;;
-    1.8) goodcode='class conftest     { void foo () { Runnable r = () -> {}; } }'
-         failcode='interface conftestfail { private void foo () {} }' ;;
-    9)   goodcode='interface conftest     { private void foo () {} }'
-         failcode='class conftestfail { public void m() { var i = new Integer(0); } }' ;;
-    10)  goodcode='class conftest     { public void m() { var i = new Integer(0); } }'
-         failcode='class conftestfail { Readable r = (var b) -> 0; }' ;;
-    11)  goodcode='class conftest     { Readable r = (var b) -> 0; }'
-         failcode='class conftestfail syntax error' ;;
+changequote(,)dnl
+    1.6 | 1.7 | 1.8 | 9 | [1-9][0-9]) ;;
+changequote([,])dnl
     *) AC_MSG_ERROR([invalid source-version argument to gt_@&t@JAVACOMP: $source_version]) ;;
   esac
   case "$target_version" in
-    1.6) cfversion=50 ;;
-    1.7) cfversion=51 ;;
-    1.8) cfversion=52 ;;
-    9)   cfversion=53 ;;
-    10)  cfversion=54 ;;
-    11)  cfversion=55 ;;
+changequote(,)dnl
+    1.6 | 1.7 | 1.8 | 9 | [1-9][0-9]) ;;
+changequote([,])dnl
     *) AC_MSG_ERROR([invalid target-version argument to gt_@&t@JAVACOMP: $target_version]) ;;
   esac
   # Function to output the classfile version of a file (8th byte) in decimal.
@@ -214,131 +209,231 @@ changequote([,])dnl
   dnl                -target 1.8 only possible with -source 1.6/1.7/1.8
   dnl                -target 9 only possible with -source 1.6/1.7/1.8/9
   dnl
-  dnl   javac 11:    -target 1.6 1.7 1.8 9 10 11  default: 11
-  dnl                -source 1.6 1.7 1.8 9 10 11  default: 11
-  dnl                -target 1.6 only possible with -source 1.6
-  dnl                -target 1.7 only possible with -source 1.6/1.7
-  dnl                -target 1.8 only possible with -source 1.6/1.7/1.8
-  dnl                -target 9 only possible with -source 1.6/1.7/1.8/9
-  dnl                -target 10 only possible with -source 1.6/1.7/1.8/9/10
+  dnl   and so on.
+  dnl   This can be summarized in this table:
   dnl
+  dnl     javac     classfile         valid -source and   obsolete -source
+  dnl     version   default version   -target values      and -target values
+  dnl     -------   ---------------   -----------------   ------------------
+  dnl     1.6       50.0              1.3 .. 1.6
+  dnl     1.7       51.0              1.3 .. 1.7
+  dnl     1.8       52.0              1.3 .. 1.8          1.3 .. 1.5
+  dnl     9         53.0              1.6 .. 9            1.6
+  dnl     10        54.0              1.6 .. 10           1.6
+  dnl     11        55.0              1.6 .. 11           1.6
+  dnl     12        56.0              1.7 .. 12           1.7
+  dnl     13        57.0              1.7 .. 13           1.7
+  dnl     14        58.0              1.7 .. 14           1.7
+  dnl     15        59.0              1.7 .. 15           1.7
+  dnl     16        60.0              1.7 .. 16           1.7
+  dnl     17        61.0              1.7 .. 17           1.7
+  dnl     18        62.0              1.7 .. 18           1.7
+  dnl     19        63.0              1.7 .. 19           1.7
+  dnl     20        64.0              1.8 .. 20           1.8
+  dnl
+  dnl   The -source option value must be <= the -target option value.
+  dnl   The minimal -source and -target option value produces an "is obsolete"
+  dnl   warning.
+  dnl
+  dnl Canonicalize source_version and target_version, for easier arithmetic.
+  case "$source_version" in
+    1.*) source_version=`echo "$source_version" | sed -e 's/^1\.//'` ;;
+  esac
+  case "$target_version" in
+    1.*) target_version=`echo "$target_version" | sed -e 's/^1\.//'` ;;
+  esac
   CONF_JAVAC=
   HAVE_JAVAC_ENVVAR=
   HAVE_JAVAC=
   HAVE_JAVACOMP=
-  echo "$goodcode" > conftest.java
-  echo "$failcode" > conftestfail.java
-  dnl If the user has set the JAVAC environment variable, use that, if it
-  dnl satisfies the constraints (possibly after adding -target and -source
-  dnl options).
-  if test -n "$JAVAC"; then
-    dnl Try $JAVAC.
-    rm -f conftest.class
-    if { echo "$as_me:__oline__: $JAVAC -d . conftest.java" >&AS_MESSAGE_LOG_FD
-         $JAVAC -d . conftest.java >&AS_MESSAGE_LOG_FD 2>&1
-       } \
-       && test -f conftest.class \
-       && expr `func_classfile_version conftest.class` '<=' $cfversion >/dev/null 2>&AS_MESSAGE_LOG_FD; then
-      dnl Try adding -source option if it is useful.
-      rm -f conftest.class
-      rm -f conftestfail.class
-      if { echo "$as_me:__oline__: $JAVAC -source $source_version -d . conftest.java" >&AS_MESSAGE_LOG_FD
-           $JAVAC -source "$source_version" -d . conftest.java >&AS_MESSAGE_LOG_FD 2>&1
-         } \
-         && test -f conftest.class \
-         && expr `func_classfile_version conftest.class` '<=' $cfversion >/dev/null 2>&AS_MESSAGE_LOG_FD \
-         && { echo "$as_me:__oline__: $JAVAC -d . conftestfail.java" >&AS_MESSAGE_LOG_FD
-              $JAVAC -d . conftestfail.java >&AS_MESSAGE_LOG_FD 2>&1
-            } \
-         && test -f conftestfail.class \
-         && ! { echo "$as_me:__oline__: $JAVAC -source $source_version -d . conftestfail.java" >&AS_MESSAGE_LOG_FD
-                $JAVAC -source "$source_version" -d . conftestfail.java >&AS_MESSAGE_LOG_FD 2>&1
-              }; then
-        CONF_JAVAC="$JAVAC -source $source_version"
-        HAVE_JAVAC_ENVVAR=1
-        HAVE_JAVACOMP=1
-      else
-        CONF_JAVAC="$JAVAC"
-        HAVE_JAVAC_ENVVAR=1
-        HAVE_JAVACOMP=1
-      fi
-    else
-      dnl Try with -target and -source options. (All supported javac
-      dnl versions support both, see the table above.)
+  dnl Sanity check.
+  if expr $source_version '<=' $target_version >/dev/null; then
+    echo 'class conftest {}' > conftest.java
+    dnl If the user has set the JAVAC environment variable, use that, if it
+    dnl satisfies the constraints (possibly after adding -target and -source
+    dnl options).
+    if test -n "$JAVAC"; then
+      dnl Test whether $JAVAC is usable.
       rm -f conftest.class
-      if { echo "$as_me:__oline__: $JAVAC -target $target_version -source $source_version -d . conftest.java" >&AS_MESSAGE_LOG_FD
-           $JAVAC -target "$target_version" -source "$source_version" -d . conftest.java >&AS_MESSAGE_LOG_FD 2>&1
+      if { echo "$as_me:__oline__: $JAVAC -d . conftest.java" >&AS_MESSAGE_LOG_FD
+           $JAVAC -d . conftest.java >&AS_MESSAGE_LOG_FD 2>&1
          } \
-         && test -f conftest.class \
-         && expr `func_classfile_version conftest.class` '<=' $cfversion >/dev/null 2>&AS_MESSAGE_LOG_FD; then
-        CONF_JAVAC="$JAVAC -target $target_version -source $source_version"
-        HAVE_JAVAC_ENVVAR=1
-        HAVE_JAVACOMP=1
+         && test -f conftest.class; then
+        compiler_cfversion=`func_classfile_version conftest.class`
+        compiler_target_version=`expr $compiler_cfversion - 44`
+        dnl It is hard to determine the compiler_source_version. This would
+        dnl require a list of code snippets that can be compiled only with a
+        dnl specific '-source' option and up, and this list would need to grow
+        dnl every 6 months.
+        dnl Also, $JAVAC may already include a '-source' option.
+        dnl Therefore, pass a '-source' option always.
+        source_option=' -source '`case "$source_version" in 6|7|8) echo 1. ;; esac`"$source_version"
+        dnl And pass a '-target' option as well, if needed.
+        dnl (All supported javac versions support both, see the table above.)
+        if expr $target_version = $compiler_target_version >/dev/null; then
+          target_option=
+        else
+          target_option=' -target '`case "$target_version" in 6|7|8) echo 1. ;; esac`"$target_version"
+        fi
+        if { echo "$as_me:__oline__: $JAVAC$source_option$target_option -d . conftest.java" >&AS_MESSAGE_LOG_FD
+             $JAVAC$source_option$target_option -d . conftest.java >&AS_MESSAGE_LOG_FD 2>&1
+           } \
+           && test -f conftest.class; then
+          dnl The compiler directly supports the desired source_version and
+          dnl target_version. Perfect.
+          CONF_JAVAC="$JAVAC$source_option$target_option"
+          HAVE_JAVAC_ENVVAR=1
+          HAVE_JAVACOMP=1
+        else
+          dnl If the desired source_version or target_version were too large
+          dnl for the compiler, there's nothing else we can do.
+          compiler_version=`echo "$as_me:__oline__: $JAVAC -version | sed -e 1q" >&AS_MESSAGE_LOG_FD
+                            $JAVAC -version | sed -e 1q`
+changequote(,)dnl
+          compiler_version=`echo "$compiler_version" | sed -e 's/^[^0-9]*\([0-9][0-9.]*\).*/\1/'`
+changequote([,])dnl
+          case "$compiler_version" in
+            1.*) dnl Map 1.6.0_85 to 6, 1.8.0_151 to 8.
+                 compiler_version=`echo "$compiler_version" | sed -e 's/^1\.//' -e 's/\..*//'`
+                 ;;
+            *) dnl Map 9.0.4 to 9, 10.0.2 to 10, etc.
+               compiler_version=`echo "$compiler_version" | sed -e 's/\..*//'`
+               ;;
+          esac
+          if expr $source_version '<=' "$compiler_version" >/dev/null \
+             && expr $target_version '<=' "$compiler_version" >/dev/null; then
+            dnl Increase $source_version and $compiler_version until the
+            dnl compiler accepts these values. This is necessary to make
+            dnl e.g. $source_version = 6 work with Java 12 or newer, or
+            dnl $source_version = 7 work with Java 20 or newer.
+            try_source_version="$source_version"
+            try_target_version="$target_version"
+            while true; do
+              dnl Invariant: $try_source_version <= $try_target_version.
+              if expr $try_source_version = $try_target_version >/dev/null; then
+                try_target_version=`expr $try_target_version + 1`
+              fi
+              try_source_version=`expr $try_source_version + 1`
+              expr $try_source_version '<=' $compiler_version >/dev/null || break
+              source_option=' -source '`case "$try_source_version" in 6|7|8) echo 1. ;; esac`"$try_source_version"
+              if expr $try_target_version = $compiler_target_version >/dev/null; then
+                target_option=
+              else
+                target_option=' -target '`case "$try_target_version" in 6|7|8) echo 1. ;; esac`"$try_target_version"
+              fi
+              if { echo "$as_me:__oline__: $JAVAC$source_option$target_option -d . conftest.java" >&AS_MESSAGE_LOG_FD
+                   $JAVAC$source_option$target_option -d . conftest.java >&AS_MESSAGE_LOG_FD 2>&1
+                 } \
+                 && test -f conftest.class; then
+                dnl The compiler supports the try_source_version and
+                dnl try_target_version. It's better than nothing.
+                CONF_JAVAC="$JAVAC$source_option$target_option"
+                HAVE_JAVAC_ENVVAR=1
+                HAVE_JAVACOMP=1
+                break
+              fi
+            done
+          fi
+        fi
       fi
     fi
-  fi
-  if test -z "$HAVE_JAVACOMP"; then
-    pushdef([AC_MSG_CHECKING],[:])dnl
-    pushdef([AC_CHECKING],[:])dnl
-    pushdef([AC_MSG_RESULT],[:])dnl
-    AC_CHECK_PROG([HAVE_JAVAC_IN_PATH], [javac], [yes])
-    popdef([AC_MSG_RESULT])dnl
-    popdef([AC_CHECKING])dnl
-    popdef([AC_MSG_CHECKING])dnl
-    if test -z "$HAVE_JAVACOMP" && test -n "$HAVE_JAVAC_IN_PATH"; then
-      dnl Test whether javac is usable.
-      if { javac -version >/dev/null 2>/dev/null || test $? -le 2; } \
-         && ( if javac -help 2>&1 >/dev/null | grep at.dms.kjc.Main >/dev/null && javac -help 2>/dev/null | grep 'released.*2000' >/dev/null ; then exit 1; else exit 0; fi ); then
-        dnl OK, javac works.
-        dnl Now test whether it supports the desired target-version and
-        dnl source-version.
+    if test -z "$HAVE_JAVACOMP"; then
+      pushdef([AC_MSG_CHECKING],[:])dnl
+      pushdef([AC_CHECKING],[:])dnl
+      pushdef([AC_MSG_RESULT],[:])dnl
+      AC_CHECK_PROG([HAVE_JAVAC_IN_PATH], [javac], [yes])
+      popdef([AC_MSG_RESULT])dnl
+      popdef([AC_CHECKING])dnl
+      popdef([AC_MSG_CHECKING])dnl
+      if test -z "$HAVE_JAVACOMP" && test -n "$HAVE_JAVAC_IN_PATH"; then
+        dnl Test whether javac is usable.
         rm -f conftest.class
         if { echo "$as_me:__oline__: javac -d . conftest.java" >&AS_MESSAGE_LOG_FD
              javac -d . conftest.java >&AS_MESSAGE_LOG_FD 2>&1
            } \
-           && test -f conftest.class \
-           && expr `func_classfile_version conftest.class` '<=' $cfversion >/dev/null 2>&AS_MESSAGE_LOG_FD; then
-          dnl Try adding -source option if it is useful.
-          rm -f conftest.class
-          rm -f conftestfail.class
-          if { echo "$as_me:__oline__: javac -source $source_version -d . conftest.java" >&AS_MESSAGE_LOG_FD
-               javac -source "$source_version" -d . conftest.java >&AS_MESSAGE_LOG_FD 2>&1
-             } \
-             && test -f conftest.class \
-             && expr `func_classfile_version conftest.class` '<=' $cfversion >/dev/null 2>&AS_MESSAGE_LOG_FD \
-             && { echo "$as_me:__oline__: javac -d . conftestfail.java" >&AS_MESSAGE_LOG_FD
-                  javac -d . conftestfail.java >&AS_MESSAGE_LOG_FD 2>&1
-                } \
-             && test -f conftestfail.class \
-             && ! { echo "$as_me:__oline__: javac -source $source_version -d . conftestfail.java" >&AS_MESSAGE_LOG_FD
-                    javac -source "$source_version" -d . conftestfail.java >&AS_MESSAGE_LOG_FD 2>&1
-                  }; then
-            CONF_JAVAC="javac -source $source_version"
-            HAVE_JAVAC=1
-            HAVE_JAVACOMP=1
+           && test -f conftest.class; then
+          compiler_cfversion=`func_classfile_version conftest.class`
+          compiler_target_version=`expr $compiler_cfversion - 44`
+          dnl It is hard to determine the compiler_source_version. This would
+          dnl require a list of code snippets that can be compiled only with a
+          dnl specific '-source' option and up, and this list would need to grow
+          dnl every 6 months.
+          dnl Also, javac may point to a shell script that already includes a
+          dnl '-source' option.
+          dnl Therefore, pass a '-source' option always.
+          source_option=' -source '`case "$source_version" in 6|7|8) echo 1. ;; esac`"$source_version"
+          dnl And pass a '-target' option as well, if needed.
+          dnl (All supported javac versions support both, see the table above.)
+          if expr $target_version = $compiler_target_version >/dev/null; then
+            target_option=
           else
-            CONF_JAVAC="javac"
-            HAVE_JAVAC=1
-            HAVE_JAVACOMP=1
+            target_option=' -target '`case "$target_version" in 6|7|8) echo 1. ;; esac`"$target_version"
           fi
-        else
-          dnl Try with -target and -source options. (All supported javac
-          dnl versions support both, see the table above.)
-          rm -f conftest.class
-          if { echo "$as_me:__oline__: javac -target $target_version -source $source_version -d . conftest.java" >&AS_MESSAGE_LOG_FD
-               javac -target "$target_version" -source "$source_version" -d . conftest.java >&AS_MESSAGE_LOG_FD 2>&1
+          if { echo "$as_me:__oline__: javac$source_option$target_option -d . conftest.java" >&AS_MESSAGE_LOG_FD
+               javac$source_option$target_option -d . conftest.java >&AS_MESSAGE_LOG_FD 2>&1
              } \
-             && test -f conftest.class \
-             && expr `func_classfile_version conftest.class` '<=' $cfversion >/dev/null 2>&AS_MESSAGE_LOG_FD; then
-            CONF_JAVAC="javac -target $target_version -source $source_version"
+             && test -f conftest.class; then
+            dnl The compiler directly supports the desired source_version and
+            dnl target_version. Perfect.
+            CONF_JAVAC="javac$source_option$target_option"
             HAVE_JAVAC=1
             HAVE_JAVACOMP=1
+          else
+            dnl If the desired source_version or target_version were too large
+            dnl for the compiler, there's nothing else we can do.
+            compiler_version=`echo "$as_me:__oline__: javac -version | sed -e 1q" >&AS_MESSAGE_LOG_FD
+                              javac -version | sed -e 1q`
+changequote(,)dnl
+            compiler_version=`echo "$compiler_version" | sed -e 's/^[^0-9]*\([0-9][0-9.]*\).*/\1/'`
+changequote([,])dnl
+            case "$compiler_version" in
+              1.*) dnl Map 1.6.0_85 to 6, 1.8.0_151 to 8.
+                   compiler_version=`echo "$compiler_version" | sed -e 's/^1\.//' -e 's/\..*//'`
+                   ;;
+              *) dnl Map 9.0.4 to 9, 10.0.2 to 10, etc.
+                 compiler_version=`echo "$compiler_version" | sed -e 's/\..*//'`
+                 ;;
+            esac
+            if expr $source_version '<=' "$compiler_version" >/dev/null \
+               && expr $target_version '<=' "$compiler_version" >/dev/null; then
+              dnl Increase $source_version and $compiler_version until the
+              dnl compiler accepts these values. This is necessary to make
+              dnl e.g. $source_version = 6 work with Java 12 or newer, or
+              dnl $source_version = 7 work with Java 20 or newer.
+              try_source_version="$source_version"
+              try_target_version="$target_version"
+              while true; do
+                dnl Invariant: $try_source_version <= $try_target_version.
+                if expr $try_source_version = $try_target_version >/dev/null; then
+                  try_target_version=`expr $try_target_version + 1`
+                fi
+                try_source_version=`expr $try_source_version + 1`
+                expr $try_source_version '<=' $compiler_version >/dev/null || break
+                source_option=' -source '`case "$try_source_version" in 6|7|8) echo 1. ;; esac`"$try_source_version"
+                if expr $try_target_version = $compiler_target_version >/dev/null; then
+                  target_option=
+                else
+                  target_option=' -target '`case "$try_target_version" in 6|7|8) echo 1. ;; esac`"$try_target_version"
+                fi
+                if { echo "$as_me:__oline__: javac$source_option$target_option -d . conftest.java" >&AS_MESSAGE_LOG_FD
+                     javac$source_option$target_option -d . conftest.java >&AS_MESSAGE_LOG_FD 2>&1
+                   } \
+                   && test -f conftest.class; then
+                  dnl The compiler supports the try_source_version and
+                  dnl try_target_version. It's better than nothing.
+                  CONF_JAVAC="javac$source_option$target_option"
+                  HAVE_JAVAC=1
+                  HAVE_JAVACOMP=1
+                  break
+                fi
+              done
+            fi
           fi
         fi
       fi
     fi
+    rm -f conftest*.java conftest*.class
   fi
-  rm -f conftest*.java conftest*.class
   if test -n "$HAVE_JAVACOMP"; then
     ac_result="$CONF_JAVAC"
   else