exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / javacomp.c
blob46944f490aff48ec807e35b39fc0ccb76467339c
1 /* Compile a Java program.
2 Copyright (C) 2001-2003, 2006-2024 Free Software Foundation, Inc.
3 Written by Bruno Haible <haible@clisp.cons.org>, 2001.
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18 #include <config.h>
19 #include <alloca.h>
21 /* Specification. */
22 #include "javacomp.h"
24 #include <errno.h>
25 #include <limits.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
33 #include "javaversion.h"
34 #include "execute.h"
35 #include "spawn-pipe.h"
36 #include "wait-process.h"
37 #include "classpath.h"
38 #include "xsetenv.h"
39 #include "sh-quote.h"
40 #include "binary-io.h"
41 #include "safe-read.h"
42 #include "xalloc.h"
43 #include "xmalloca.h"
44 #include "concat-filename.h"
45 #include "fwriteerror.h"
46 #include "clean-temp.h"
47 #include <error.h>
48 #include "xvasprintf.h"
49 #include "verify.h"
50 #include "c-strstr.h"
51 #include "gettext.h"
53 #define _(str) gettext (str)
56 /* Survey of Java compilers.
58 A = does it work without CLASSPATH being set
59 C = option to set CLASSPATH, other than setting it in the environment
60 O = option for optimizing
61 g = option for debugging
62 T = test for presence
64 Program from A C O g T
66 $JAVAC unknown N n/a -O -g true
67 javac JDK 1.1.8 Y -classpath P -O -g javac 2>/dev/null; test $? = 1
68 javac JDK 1.3.0 Y -classpath P -O -g javac 2>/dev/null; test $? -le 2
70 All compilers support the option "-d DIRECTORY" for the base directory
71 of the classes to be written.
73 The CLASSPATH is a colon separated list of pathnames. (On Windows: a
74 semicolon separated list of pathnames.)
76 We try the Java compilers in the following order:
77 1. getenv ("JAVAC"), because the user must be able to override our
78 preferences,
79 2. "javac", because it is a standard compiler.
81 We unset the JAVA_HOME environment variable, because a wrong setting of
82 this variable can confuse the JDK's javac.
85 /* Return the default target_version. */
86 static const char *
87 default_target_version (void)
89 /* Use a cache. Assumes that the PATH environment variable doesn't change
90 during the lifetime of the program. */
91 static const char *java_version_cache;
92 if (java_version_cache == NULL)
94 /* Determine the version from the found JVM. */
95 java_version_cache = javaexec_version ();
96 if (java_version_cache == NULL)
97 java_version_cache = "1.6";
98 else if (java_version_cache[0] == '1'
99 && java_version_cache[1] == '.'
100 && java_version_cache[2] >= '1' && java_version_cache[2] <= '5'
101 && java_version_cache[3] == '\0')
103 error (0, 0, _("The java program is too old. Cannot compile Java code for this old version any more."));
104 java_version_cache = "1.6";
106 else if ((java_version_cache[0] == '1'
107 && java_version_cache[1] == '.'
108 && java_version_cache[2] >= '6' && java_version_cache[2] <= '8'
109 && java_version_cache[3] == '\0')
110 || (java_version_cache[0] == '9'
111 && java_version_cache[1] == '\0')
112 || ((java_version_cache[0] >= '1'
113 && java_version_cache[0] <= '9')
114 && (java_version_cache[1] >= '0'
115 && java_version_cache[1] <= '9')
116 && java_version_cache[2] == '\0'))
117 /* Here we could choose any target_version between source_version and
118 the java_version_cache. (If it is too small, it will be incremented
119 below until it works.) Since we documented in javacomp.h that it is
120 determined from the JVM, we do that. */
122 else
123 java_version_cache = "1.6";
125 return java_version_cache;
128 /* ======================= Source version dependent ======================= */
130 /* Convert a source version to an index. */
131 #define SOURCE_VERSION_BOUND 94 /* exclusive upper bound */
132 static unsigned int
133 source_version_index (const char *source_version)
135 if (source_version[0] == '1' && source_version[1] == '.')
137 if ((source_version[2] >= '6' && source_version[2] <= '8')
138 && source_version[3] == '\0')
139 return source_version[2] - '6';
141 else if (source_version[0] == '9' && source_version[1] == '\0')
142 return 3;
143 else if ((source_version[0] >= '1' && source_version[0] <= '9')
144 && (source_version[1] >= '0' && source_version[1] <= '9')
145 && source_version[2] == '\0')
146 return (source_version[0] - '1') * 10 + source_version[1] - '0' + 4;
147 error (EXIT_FAILURE, 0, _("invalid source_version argument to compile_java_class"));
148 return 0;
151 /* ======================= Target version dependent ======================= */
153 /* Convert a target version to an index. */
154 #define TARGET_VERSION_BOUND 94 /* exclusive upper bound */
155 static unsigned int
156 target_version_index (const char *target_version)
158 if (target_version[0] == '1' && target_version[1] == '.'
159 && (target_version[2] >= '6' && target_version[2] <= '8')
160 && target_version[3] == '\0')
161 return target_version[2] - '6';
162 else if (target_version[0] == '9' && target_version[1] == '\0')
163 return 3;
164 else if ((target_version[0] >= '1' && target_version[0] <= '9')
165 && (target_version[1] >= '0' && target_version[1] <= '9')
166 && target_version[2] == '\0')
167 return (target_version[0] - '1') * 10 + target_version[1] - '0' + 4;
168 error (EXIT_FAILURE, 0, _("invalid target_version argument to compile_java_class"));
169 return 0;
172 /* ======================== Compilation subroutines ======================== */
174 /* Try to compile a set of Java sources with $JAVAC.
175 Return a failure indicator (true upon error). */
176 static bool
177 compile_using_envjavac (const char *javac,
178 const char * const *java_sources,
179 unsigned int java_sources_count,
180 const char *directory,
181 bool optimize, bool debug,
182 bool verbose, bool null_stderr)
184 /* Because $JAVAC may consist of a command and options, we use the
185 shell. Because $JAVAC has been set by the user, we leave all
186 environment variables in place, including JAVA_HOME, and we don't
187 erase the user's CLASSPATH. */
188 bool err;
189 unsigned int command_length;
190 char *command;
191 const char *argv[4];
192 int exitstatus;
193 unsigned int i;
194 char *p;
196 command_length = strlen (javac);
197 if (optimize)
198 command_length += 3;
199 if (debug)
200 command_length += 3;
201 if (directory != NULL)
202 command_length += 4 + shell_quote_length (directory);
203 for (i = 0; i < java_sources_count; i++)
204 command_length += 1 + shell_quote_length (java_sources[i]);
205 command_length += 1;
207 command = (char *) xmalloca (command_length);
208 p = command;
209 /* Don't shell_quote $JAVAC, because it may consist of a command
210 and options. */
211 memcpy (p, javac, strlen (javac));
212 p += strlen (javac);
213 if (optimize)
215 memcpy (p, " -O", 3);
216 p += 3;
218 if (debug)
220 memcpy (p, " -g", 3);
221 p += 3;
223 if (directory != NULL)
225 memcpy (p, " -d ", 4);
226 p += 4;
227 p = shell_quote_copy (p, directory);
229 for (i = 0; i < java_sources_count; i++)
231 *p++ = ' ';
232 p = shell_quote_copy (p, java_sources[i]);
234 *p++ = '\0';
235 /* Ensure command_length was correctly calculated. */
236 if (p - command > command_length)
237 abort ();
239 if (verbose)
240 printf ("%s\n", command);
242 argv[0] = BOURNE_SHELL;
243 argv[1] = "-c";
244 argv[2] = command;
245 argv[3] = NULL;
246 exitstatus = execute (javac, BOURNE_SHELL, argv, NULL,
247 false, false, false, null_stderr,
248 true, true, NULL);
249 err = (exitstatus != 0);
251 freea (command);
253 return err;
256 /* Try to compile a set of Java sources with javac.
257 Return a failure indicator (true upon error). */
258 static bool
259 compile_using_javac (const char * const *java_sources,
260 unsigned int java_sources_count,
261 const char *nowarn_option,
262 bool source_option, const char *source_version,
263 bool target_option, const char *target_version,
264 const char *directory,
265 bool optimize, bool debug,
266 bool verbose, bool null_stderr)
268 bool err;
269 unsigned int argc;
270 const char **argv;
271 const char **argp;
272 int exitstatus;
273 unsigned int i;
275 argc =
276 1 + (nowarn_option != NULL ? 1 : 0) + (source_option ? 2 : 0)
277 + (target_option ? 2 : 0) + (optimize ? 1 : 0) + (debug ? 1 : 0)
278 + (directory != NULL ? 2 : 0) + java_sources_count;
279 argv = (const char **) xmalloca ((argc + 1) * sizeof (const char *));
281 argp = argv;
282 *argp++ = "javac";
283 if (nowarn_option != NULL)
284 *argp++ = nowarn_option;
285 if (source_option)
287 *argp++ = "-source";
288 *argp++ = source_version;
290 if (target_option)
292 *argp++ = "-target";
293 *argp++ = target_version;
295 if (optimize)
296 *argp++ = "-O";
297 if (debug)
298 *argp++ = "-g";
299 if (directory != NULL)
301 *argp++ = "-d";
302 *argp++ = directory;
304 for (i = 0; i < java_sources_count; i++)
305 *argp++ = java_sources[i];
306 *argp = NULL;
307 /* Ensure argv length was correctly calculated. */
308 if (argp - argv != argc)
309 abort ();
311 if (verbose)
313 char *command = shell_quote_argv (argv);
314 printf ("%s\n", command);
315 free (command);
318 exitstatus = execute ("javac", "javac", argv, NULL,
319 false, false, false,
320 null_stderr, true, true, NULL);
321 err = (exitstatus != 0);
323 freea (argv);
325 return err;
328 /* ====================== Usability test subroutines ====================== */
330 /* Executes a program.
331 Returns the first line of its output, as a freshly allocated string, or
332 NULL. */
333 static char *
334 execute_and_read_line (const char *progname,
335 const char *prog_path, const char * const *prog_argv)
337 pid_t child;
338 int fd[1];
339 FILE *fp;
340 char *line;
341 size_t linesize;
342 size_t linelen;
343 int exitstatus;
345 /* Open a pipe to the program. */
346 child = create_pipe_in (progname, prog_path, prog_argv, NULL,
347 DEV_NULL, false, true, false, fd);
349 if (child == -1)
350 return NULL;
352 /* Retrieve its result. */
353 fp = fdopen (fd[0], "r");
354 if (fp == NULL)
356 error (0, errno, _("fdopen() failed"));
357 return NULL;
360 line = NULL; linesize = 0;
361 linelen = getline (&line, &linesize, fp);
362 if (linelen == (size_t)(-1))
364 error (0, 0, _("%s subprocess I/O error"), progname);
365 return NULL;
367 if (linelen > 0 && line[linelen - 1] == '\n')
368 line[linelen - 1] = '\0';
370 /* Read until EOF (otherwise the child process may get a SIGPIPE signal). */
371 while (getc (fp) != EOF)
374 fclose (fp);
376 /* Remove zombie process from process list, and retrieve exit status. */
377 exitstatus =
378 wait_subprocess (child, progname, true, false, true, false, NULL);
379 if (exitstatus != 0)
381 free (line);
382 return NULL;
385 return line;
388 /* Executes a program, assumed to be a Java compiler with '-version' option.
389 Returns the version number. */
390 static unsigned int
391 get_compiler_version (const char *progname,
392 const char *prog_path, const char * const *prog_argv)
394 char *line = execute_and_read_line (progname, prog_path, prog_argv);
395 if (line == NULL)
396 return 0;
398 /* Search the first digit in line. */
399 char *version_start = line;
400 for (version_start = line; ; version_start++)
402 if (*version_start == '\0')
404 /* No digits found. */
405 free (line);
406 return 0;
408 if (*version_start >= '0' && *version_start <= '9')
409 break;
412 /* Search the end of the version string. */
413 char *version_end = version_start;
414 while ((*version_end >= '0' && *version_end <= '9') || *version_end == '.')
415 version_end++;
416 *version_end = '\0';
418 /* 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. */
419 if (version_start[0] == '1' && version_start[1] == '.')
420 version_start += 2;
421 version_end = strchr (version_start, '.');
422 if (version_end != NULL)
423 *version_end = '\0';
425 /* Convert number to 'unsigned int'. */
426 unsigned int result;
427 switch (strlen (version_start))
429 case 1:
430 result = version_start[0] - '0';
431 break;
433 case 2:
434 result = (version_start[0] - '0') * 10 + (version_start[1] - '0');
435 break;
437 default:
438 result = 0;
441 free (line);
442 return result;
445 /* Write a given contents to a temporary file.
446 FILE_NAME is the name of a file inside TMPDIR that is known not to exist
447 yet.
448 Return a failure indicator (true upon error). */
449 static bool
450 write_temp_file (struct temp_dir *tmpdir, const char *file_name,
451 const char *contents)
453 FILE *fp;
455 register_temp_file (tmpdir, file_name);
456 fp = fopen_temp (file_name, "we", false);
457 if (fp == NULL)
459 error (0, errno, _("failed to create \"%s\""), file_name);
460 unregister_temp_file (tmpdir, file_name);
461 return true;
463 fputs (contents, fp);
464 if (fwriteerror_temp (fp))
466 error (0, errno, _("error while writing \"%s\" file"), file_name);
467 return true;
469 return false;
472 /* Return the class file version number of a class file on disk. */
473 static int
474 get_classfile_version (const char *compiled_file_name)
476 unsigned char header[8];
477 int fd;
479 /* Open the class file. */
480 fd = open (compiled_file_name, O_RDONLY | O_BINARY | O_CLOEXEC, 0);
481 if (fd >= 0)
483 /* Read its first 8 bytes. */
484 if (safe_read (fd, header, 8) == 8)
486 /* Verify the class file signature. */
487 if (header[0] == 0xCA && header[1] == 0xFE
488 && header[2] == 0xBA && header[3] == 0xBE)
490 close (fd);
491 return header[7];
494 close (fd);
497 /* Could not get the class file version. Return a very large one. */
498 return INT_MAX;
501 /* Test whether $JAVAC can be used, and whether it needs a -source and/or
502 -target option, as well as an option to inhibit warnings.
503 Return a failure indicator (true upon error). */
504 static bool
505 is_envjavac_usable (const char *javac,
506 const char *source_version, const char *target_version,
507 bool *usablep,
508 char nowarn_option_out[17],
509 char source_option_out[30], char target_option_out[30])
511 /* The cache depends on the source_version and target_version. */
512 struct result_t
514 /*bool*/ unsigned int tested : 1;
515 /*bool*/ unsigned int usable : 1;
516 /*bool*/ unsigned int nowarn_option : 1;
517 unsigned int source_option : 7;
518 unsigned int target_option : 7;
520 static struct result_t result_cache[SOURCE_VERSION_BOUND][TARGET_VERSION_BOUND];
521 struct result_t *resultp;
523 resultp = &result_cache[source_version_index (source_version)]
524 [target_version_index (target_version)];
525 if (!resultp->tested)
527 /* Canonicalize source_version and target_version, for easier
528 arithmetic. */
529 int try_source_version = 6 + source_version_index (source_version);
530 int try_target_version = 6 + target_version_index (target_version);
531 /* Sanity check. */
532 if (try_source_version <= try_target_version)
534 /* Try $JAVAC. */
535 struct temp_dir *tmpdir;
536 char *conftest_file_name;
537 char *compiled_file_name;
538 const char *java_sources[1];
539 const char *nowarn_option;
540 struct stat statbuf;
542 tmpdir = create_temp_dir ("java", NULL, false);
543 if (tmpdir == NULL)
544 return true;
546 conftest_file_name =
547 xconcatenated_filename (tmpdir->dir_name, "conftest.java", NULL);
548 if (write_temp_file (tmpdir, conftest_file_name, "class conftest {}"))
550 free (conftest_file_name);
551 cleanup_temp_dir (tmpdir);
552 return true;
555 compiled_file_name =
556 xconcatenated_filename (tmpdir->dir_name, "conftest.class", NULL);
557 register_temp_file (tmpdir, compiled_file_name);
559 /* See the discussion in javacomp.m4. */
560 nowarn_option = " -Xlint:-options";
561 char *javac_nowarn = xasprintf ("%s%s", javac, nowarn_option);
562 assume (javac_nowarn != NULL);
564 java_sources[0] = conftest_file_name;
565 if ((!compile_using_envjavac (javac_nowarn,
566 java_sources, 1, tmpdir->dir_name,
567 false, false, false, true)
568 && stat (compiled_file_name, &statbuf) >= 0)
569 || (nowarn_option = "",
570 unlink (compiled_file_name),
571 (!compile_using_envjavac (javac,
572 java_sources, 1, tmpdir->dir_name,
573 false, false, false, true)
574 && stat (compiled_file_name, &statbuf) >= 0)))
576 /* $JAVAC compiled conftest.java successfully. */
577 int compiler_cfversion =
578 get_classfile_version (compiled_file_name);
579 int compiler_target_version = compiler_cfversion - 44;
581 /* It is hard to determine the compiler_source_version. This
582 would require a list of code snippets that can be compiled only
583 with a specific '-source' option and up, and this list would
584 need to grow every 6 months.
585 Also, $JAVAC may already include a '-source' option.
586 Therefore, pass a '-source' option always. */
587 char source_option[30];
588 sprintf (source_option, " -source %s%d",
589 try_source_version <= 8 ? "1." : "",
590 try_source_version);
592 /* And pass a '-target' option as well, if needed.
593 (All supported javac versions support both, see the table in
594 javacomp.m4.) */
595 char target_option[30];
596 if (try_target_version == compiler_target_version)
597 target_option[0] = '\0';
598 else
599 sprintf (target_option, " -target %s%d",
600 try_target_version <= 8 ? "1." : "",
601 try_target_version);
603 char *javac_source_target =
604 xasprintf ("%s%s%s%s", javac, nowarn_option,
605 source_option, target_option);
606 assume (javac_source_target != NULL);
608 unlink (compiled_file_name);
610 java_sources[0] = conftest_file_name;
611 if (!compile_using_envjavac (javac_source_target,
612 java_sources, 1, tmpdir->dir_name,
613 false, false, false, true)
614 && stat (compiled_file_name, &statbuf) >= 0)
616 /* The compiler directly supports the desired source_version
617 and target_version. Perfect. */
618 free (javac_source_target);
620 resultp->nowarn_option = (nowarn_option[0] != '\0');
621 resultp->source_option = try_source_version;
622 resultp->target_option =
623 (try_target_version == compiler_target_version ? 0 :
624 try_target_version);
625 resultp->usable = true;
627 else
629 /* If the desired source_version or target_version were too
630 large for the compiler, there's nothing else we can do. */
631 unsigned int compiler_version;
633 free (javac_source_target);
636 size_t command_length;
637 char *command;
638 const char *argv[4];
640 command_length = strlen (javac) + 9 + 1;
642 command = (char *) xmalloca (command_length);
644 char *p = command;
645 p = stpcpy (p, javac);
646 p = stpcpy (p, " -version");
647 *p++ = '\0';
648 /* Ensure command_length was correctly calculated. */
649 if (p - command > command_length)
650 abort ();
653 argv[0] = BOURNE_SHELL;
654 argv[1] = "-c";
655 argv[2] = command;
656 argv[3] = NULL;
657 compiler_version =
658 get_compiler_version (javac, BOURNE_SHELL, argv);
660 freea (command);
663 if (try_source_version <= compiler_version
664 && try_target_version <= compiler_version)
666 /* Increase try_source_version and compiler_version until
667 the compiler accepts these values. This is necessary
668 to make e.g. try_source_version = 6 work with Java 12
669 or newer, or try_source_version = 7 work with Java 20
670 or newer. */
671 for (;;)
673 /* Invariant:
674 try_source_version <= try_target_version. */
675 if (try_source_version == try_target_version)
676 try_target_version++;
677 try_source_version++;
678 if (try_source_version > compiler_version)
679 break;
681 sprintf (source_option, " -source %s%d",
682 try_source_version <= 8 ? "1." : "",
683 try_source_version);
685 if (try_target_version == compiler_target_version)
686 target_option[0] = '\0';
687 else
688 sprintf (target_option, " -target %s%d",
689 try_target_version <= 8 ? "1." : "",
690 try_target_version);
692 javac_source_target =
693 xasprintf ("%s%s%s%s", javac, nowarn_option,
694 source_option, target_option);
695 assume (javac_source_target != NULL);
697 unlink (compiled_file_name);
699 java_sources[0] = conftest_file_name;
700 if (!compile_using_envjavac (javac_source_target,
701 java_sources, 1,
702 tmpdir->dir_name,
703 false, false,
704 false, true)
705 && stat (compiled_file_name, &statbuf) >= 0)
707 /* The compiler supports the try_source_version
708 and try_target_version. It's better than
709 nothing. */
710 free (javac_source_target);
712 resultp->nowarn_option = (nowarn_option[0] != '\0');
713 resultp->source_option = try_source_version;
714 resultp->target_option =
715 (try_target_version == compiler_target_version ? 0 :
716 try_target_version);
717 resultp->usable = true;
718 break;
721 free (javac_source_target);
727 cleanup_temp_dir (tmpdir);
729 free (javac_nowarn);
730 free (compiled_file_name);
731 free (conftest_file_name);
734 resultp->tested = true;
737 *usablep = resultp->usable;
738 if (resultp->nowarn_option)
739 strcpy (nowarn_option_out, " -Xlint:-options");
740 else
741 nowarn_option_out[0] = '\0';
742 sprintf (source_option_out, " -source %s%d",
743 resultp->source_option <= 8 ? "1." : "",
744 resultp->source_option);
745 if (resultp->target_option == 0)
746 target_option_out[0] = '\0';
747 else
748 sprintf (target_option_out, " -target %s%d",
749 resultp->target_option <= 8 ? "1." : "",
750 resultp->target_option);
751 return false;
754 static bool
755 is_javac_present (void)
757 static bool javac_tested;
758 static bool javac_present;
760 if (!javac_tested)
762 /* Test for presence of javac: "javac 2> /dev/null ; test $? -le 2" */
763 const char *argv[2];
764 int exitstatus;
766 argv[0] = "javac";
767 argv[1] = NULL;
768 exitstatus = execute ("javac", "javac", argv, NULL,
769 false, false, true, true,
770 true, false, NULL);
771 javac_present = (exitstatus == 0 || exitstatus == 1 || exitstatus == 2);
772 javac_tested = true;
775 return javac_present;
778 /* Test whether javac can be used and whether it needs a -source and/or
779 -target option, as well as an option to inhibit warnings.
780 Return a failure indicator (true upon error). */
781 static bool
782 is_javac_usable (const char *source_version, const char *target_version,
783 bool *usablep,
784 char nowarn_option_out[17],
785 char source_option_out[20], char target_option_out[20])
787 /* The cache depends on the source_version and target_version. */
788 struct result_t
790 /*bool*/ unsigned int tested : 1;
791 /*bool*/ unsigned int usable : 1;
792 /*bool*/ unsigned int nowarn_option : 1;
793 unsigned int source_option : 7;
794 unsigned int target_option : 7;
796 static struct result_t result_cache[SOURCE_VERSION_BOUND][TARGET_VERSION_BOUND];
797 struct result_t *resultp;
799 resultp = &result_cache[source_version_index (source_version)]
800 [target_version_index (target_version)];
801 if (!resultp->tested)
803 /* Canonicalize source_version and target_version, for easier
804 arithmetic. */
805 int try_source_version = 6 + source_version_index (source_version);
806 int try_target_version = 6 + target_version_index (target_version);
807 /* Sanity check. */
808 if (try_source_version <= try_target_version)
810 /* Try javac. */
811 struct temp_dir *tmpdir;
812 char *conftest_file_name;
813 char *compiled_file_name;
814 const char *java_sources[1];
815 const char *nowarn_option;
816 struct stat statbuf;
818 tmpdir = create_temp_dir ("java", NULL, false);
819 if (tmpdir == NULL)
820 return true;
822 conftest_file_name =
823 xconcatenated_filename (tmpdir->dir_name, "conftest.java", NULL);
824 if (write_temp_file (tmpdir, conftest_file_name, "class conftest {}"))
826 free (conftest_file_name);
827 cleanup_temp_dir (tmpdir);
828 return true;
831 compiled_file_name =
832 xconcatenated_filename (tmpdir->dir_name, "conftest.class", NULL);
833 register_temp_file (tmpdir, compiled_file_name);
835 /* See the discussion in javacomp.m4. */
836 nowarn_option = "-Xlint:-options";
838 java_sources[0] = conftest_file_name;
839 if ((!compile_using_javac (java_sources, 1,
840 nowarn_option,
841 false, source_version,
842 false, target_version,
843 tmpdir->dir_name,
844 false, false, false, true)
845 && stat (compiled_file_name, &statbuf) >= 0)
846 || (nowarn_option = NULL,
847 unlink (compiled_file_name),
848 (!compile_using_javac (java_sources, 1,
849 nowarn_option,
850 false, source_version,
851 false, target_version,
852 tmpdir->dir_name,
853 false, false, false, true)
854 && stat (compiled_file_name, &statbuf) >= 0)))
856 /* javac compiled conftest.java successfully. */
857 int compiler_cfversion =
858 get_classfile_version (compiled_file_name);
859 int compiler_target_version = compiler_cfversion - 44;
861 /* It is hard to determine the compiler_source_version. This
862 would require a list of code snippets that can be compiled only
863 with a specific '-source' option and up, and this list would
864 need to grow every 6 months.
865 Also, javac may point to a shell script that already includes a
866 '-source' option.
867 Therefore, pass a '-source' option always. */
868 char source_option[20];
869 sprintf (source_option, "%s%d",
870 try_source_version <= 8 ? "1." : "",
871 try_source_version);
873 /* And pass a '-target' option as well, if needed.
874 (All supported javac versions support both, see the table in
875 javacomp.m4.) */
876 char target_option[20];
877 sprintf (target_option, "%s%d",
878 try_target_version <= 8 ? "1." : "",
879 try_target_version);
881 unlink (compiled_file_name);
883 java_sources[0] = conftest_file_name;
884 if (!compile_using_javac (java_sources, 1,
885 nowarn_option,
886 true,
887 source_option,
888 try_target_version != compiler_target_version,
889 target_option,
890 tmpdir->dir_name,
891 false, false, false, true)
892 && stat (compiled_file_name, &statbuf) >= 0)
894 /* The compiler directly supports the desired source_version
895 and target_version. Perfect. */
896 resultp->nowarn_option = (nowarn_option != NULL);
897 resultp->source_option = try_source_version;
898 resultp->target_option =
899 (try_target_version == compiler_target_version ? 0 :
900 try_target_version);
901 resultp->usable = true;
903 else
905 /* If the desired source_version or target_version were too
906 large for the compiler, there's nothing else we can do. */
907 unsigned int compiler_version;
910 const char *argv[3];
912 argv[0] = "javac";
913 argv[1] = "-version";
914 argv[2] = NULL;
915 compiler_version =
916 get_compiler_version ("javac", argv[0], argv);
919 if (try_source_version <= compiler_version
920 && try_target_version <= compiler_version)
922 /* Increase try_source_version and compiler_version until
923 the compiler accepts these values. This is necessary
924 to make e.g. try_source_version = 6 work with Java 12
925 or newer, or try_source_version = 7 work with Java 20
926 or newer. */
927 for (;;)
929 /* Invariant:
930 try_source_version <= try_target_version. */
931 if (try_source_version == try_target_version)
932 try_target_version++;
933 try_source_version++;
934 if (try_source_version > compiler_version)
935 break;
937 sprintf (source_option, "%s%d",
938 try_source_version <= 8 ? "1." : "",
939 try_source_version);
941 sprintf (target_option, "%s%d",
942 try_target_version <= 8 ? "1." : "",
943 try_target_version);
945 unlink (compiled_file_name);
947 java_sources[0] = conftest_file_name;
948 if (!compile_using_javac (java_sources, 1,
949 nowarn_option,
950 true,
951 source_option,
952 try_target_version != compiler_target_version,
953 target_option,
954 tmpdir->dir_name,
955 false, false, false, true)
956 && stat (compiled_file_name, &statbuf) >= 0)
958 /* The compiler supports the try_source_version
959 and try_target_version. It's better than
960 nothing. */
961 resultp->nowarn_option = (nowarn_option != NULL);
962 resultp->source_option = try_source_version;
963 resultp->target_option =
964 (try_target_version == compiler_target_version ? 0 :
965 try_target_version);
966 resultp->usable = true;
967 break;
974 cleanup_temp_dir (tmpdir);
976 free (compiled_file_name);
977 free (conftest_file_name);
980 resultp->tested = true;
983 *usablep = resultp->usable;
984 if (resultp->nowarn_option)
985 strcpy (nowarn_option_out, "-Xlint:-options");
986 else
987 nowarn_option_out[0] = '\0';
988 sprintf (source_option_out, "%s%d",
989 resultp->source_option <= 8 ? "1." : "",
990 resultp->source_option);
991 if (resultp->target_option == 0)
992 target_option_out[0] = '\0';
993 else
994 sprintf (target_option_out, "%s%d",
995 resultp->target_option <= 8 ? "1." : "",
996 resultp->target_option);
997 return false;
1000 /* ============================= Main function ============================= */
1002 bool
1003 compile_java_class (const char * const *java_sources,
1004 unsigned int java_sources_count,
1005 const char * const *classpaths,
1006 unsigned int classpaths_count,
1007 const char *source_version,
1008 const char *target_version,
1009 const char *directory,
1010 bool optimize, bool debug,
1011 bool use_minimal_classpath,
1012 bool verbose)
1014 bool err = false;
1015 char *old_JAVA_HOME;
1017 /* Map source_version 1.1 ... 1.5 to 1.6. */
1018 if (source_version[0] == '1' && source_version[1] == '.'
1019 && (source_version[2] >= '1' && source_version[2] <= '5')
1020 && source_version[3] == '\0')
1021 source_version = "1.6";
1023 /* Map target_version 1.1 ... 1.5 to 1.6. */
1024 if (target_version != NULL
1025 && target_version[0] == '1' && target_version[1] == '.'
1026 && (target_version[2] >= '1' && target_version[2] <= '5')
1027 && target_version[3] == '\0')
1028 target_version = "1.6";
1031 const char *javac = getenv ("JAVAC");
1032 if (javac != NULL && javac[0] != '\0')
1034 bool usable = false;
1035 char nowarn_option[17];
1036 char source_option[30];
1037 char target_option[30];
1039 if (target_version == NULL)
1040 target_version = default_target_version ();
1042 if (is_envjavac_usable (javac,
1043 source_version, target_version,
1044 &usable,
1045 nowarn_option,
1046 source_option, target_option))
1048 err = true;
1049 goto done1;
1052 if (usable)
1054 char *old_classpath;
1055 char *javac_with_options;
1057 /* Set CLASSPATH. */
1058 old_classpath =
1059 set_classpath (classpaths, classpaths_count, false, verbose);
1061 javac_with_options =
1062 xasprintf ("%s%s%s%s", javac,
1063 nowarn_option, source_option, target_option);
1064 assume (javac_with_options != NULL);
1066 err = compile_using_envjavac (javac_with_options,
1067 java_sources, java_sources_count,
1068 directory, optimize, debug, verbose,
1069 false);
1071 free (javac_with_options);
1073 /* Reset CLASSPATH. */
1074 reset_classpath (old_classpath);
1076 goto done1;
1081 /* Unset the JAVA_HOME environment variable. */
1082 old_JAVA_HOME = getenv ("JAVA_HOME");
1083 if (old_JAVA_HOME != NULL)
1085 old_JAVA_HOME = xstrdup (old_JAVA_HOME);
1086 unsetenv ("JAVA_HOME");
1089 if (is_javac_present ())
1091 bool usable = false;
1092 char nowarn_option[17];
1093 char source_option[20];
1094 char target_option[20];
1096 if (target_version == NULL)
1097 target_version = default_target_version ();
1099 if (is_javac_usable (source_version, target_version,
1100 &usable,
1101 nowarn_option,
1102 source_option, target_option))
1104 err = true;
1105 goto done1;
1108 if (usable)
1110 char *old_classpath;
1112 /* Set CLASSPATH. We don't use the "-classpath ..." option because
1113 in JDK 1.1.x its argument should also contain the JDK's
1114 classes.zip, but we don't know its location. (In JDK 1.3.0 it
1115 would work.) */
1116 old_classpath =
1117 set_classpath (classpaths, classpaths_count, use_minimal_classpath,
1118 verbose);
1120 err = compile_using_javac (java_sources, java_sources_count,
1121 nowarn_option[0] != '\0' ? nowarn_option : NULL,
1122 true, source_option,
1123 target_option[0] != '\0', target_option,
1124 directory, optimize, debug, verbose,
1125 false);
1127 /* Reset CLASSPATH. */
1128 reset_classpath (old_classpath);
1130 goto done2;
1134 error (0, 0, _("Java compiler not found, try setting $JAVAC"));
1135 err = true;
1137 done2:
1138 if (old_JAVA_HOME != NULL)
1140 xsetenv ("JAVA_HOME", old_JAVA_HOME, 1);
1141 free (old_JAVA_HOME);
1144 done1:
1145 return err;