gettext: Sync with gettext 0.23.
[gnulib.git] / lib / javacomp.c
blobc5b90147ca2a8c96b5e7795096bafe0f5ac0218e
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, 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, 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, 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)
355 error (EXIT_FAILURE, errno, _("fdopen() failed"));
357 line = NULL; linesize = 0;
358 linelen = getline (&line, &linesize, fp);
359 if (linelen == (size_t)(-1))
361 error (0, 0, _("%s subprocess I/O error"), progname);
362 return NULL;
364 if (linelen > 0 && line[linelen - 1] == '\n')
365 line[linelen - 1] = '\0';
367 /* Read until EOF (otherwise the child process may get a SIGPIPE signal). */
368 while (getc (fp) != EOF)
371 fclose (fp);
373 /* Remove zombie process from process list, and retrieve exit status. */
374 exitstatus =
375 wait_subprocess (child, progname, true, false, true, false, NULL);
376 if (exitstatus != 0)
378 free (line);
379 return NULL;
382 return line;
385 /* Executes a program, assumed to be a Java compiler with '-version' option.
386 Returns the version number. */
387 static unsigned int
388 get_compiler_version (const char *progname,
389 const char *prog_path, const char * const *prog_argv)
391 char *line = execute_and_read_line (progname, prog_path, prog_argv);
392 if (line == NULL)
393 return 0;
395 /* Search the first digit in line. */
396 char *version_start = line;
397 for (version_start = line; ; version_start++)
399 if (*version_start == '\0')
401 /* No digits found. */
402 free (line);
403 return 0;
405 if (*version_start >= '0' && *version_start <= '9')
406 break;
409 /* Search the end of the version string. */
410 char *version_end = version_start;
411 while ((*version_end >= '0' && *version_end <= '9') || *version_end == '.')
412 version_end++;
413 *version_end = '\0';
415 /* 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. */
416 if (version_start[0] == '1' && version_start[1] == '.')
417 version_start += 2;
418 version_end = strchr (version_start, '.');
419 if (version_end != NULL)
420 *version_end = '\0';
422 /* Convert number to 'unsigned int'. */
423 unsigned int result;
424 switch (strlen (version_start))
426 case 1:
427 result = version_start[0] - '0';
428 break;
430 case 2:
431 result = (version_start[0] - '0') * 10 + (version_start[1] - '0');
432 break;
434 default:
435 result = 0;
438 free (line);
439 return result;
442 /* Write a given contents to a temporary file.
443 FILE_NAME is the name of a file inside TMPDIR that is known not to exist
444 yet.
445 Return a failure indicator (true upon error). */
446 static bool
447 write_temp_file (struct temp_dir *tmpdir, const char *file_name,
448 const char *contents)
450 FILE *fp;
452 register_temp_file (tmpdir, file_name);
453 fp = fopen_temp (file_name, "we", false);
454 if (fp == NULL)
456 error (0, errno, _("failed to create \"%s\""), file_name);
457 unregister_temp_file (tmpdir, file_name);
458 return true;
460 fputs (contents, fp);
461 if (fwriteerror_temp (fp))
463 error (0, errno, _("error while writing \"%s\" file"), file_name);
464 return true;
466 return false;
469 /* Return the class file version number of a class file on disk. */
470 static int
471 get_classfile_version (const char *compiled_file_name)
473 unsigned char header[8];
474 int fd;
476 /* Open the class file. */
477 fd = open (compiled_file_name, O_RDONLY | O_BINARY | O_CLOEXEC, 0);
478 if (fd >= 0)
480 /* Read its first 8 bytes. */
481 if (safe_read (fd, header, 8) == 8)
483 /* Verify the class file signature. */
484 if (header[0] == 0xCA && header[1] == 0xFE
485 && header[2] == 0xBA && header[3] == 0xBE)
487 close (fd);
488 return header[7];
491 close (fd);
494 /* Could not get the class file version. Return a very large one. */
495 return INT_MAX;
498 /* Test whether $JAVAC can be used, and whether it needs a -source and/or
499 -target option, as well as an option to inhibit warnings.
500 Return a failure indicator (true upon error). */
501 static bool
502 is_envjavac_usable (const char *javac,
503 const char *source_version, const char *target_version,
504 bool *usablep,
505 char nowarn_option_out[17],
506 char source_option_out[30], char target_option_out[30])
508 /* The cache depends on the source_version and target_version. */
509 struct result_t
511 /*bool*/ unsigned int tested : 1;
512 /*bool*/ unsigned int usable : 1;
513 /*bool*/ unsigned int nowarn_option : 1;
514 unsigned int source_option : 7;
515 unsigned int target_option : 7;
517 static struct result_t result_cache[SOURCE_VERSION_BOUND][TARGET_VERSION_BOUND];
518 struct result_t *resultp;
520 resultp = &result_cache[source_version_index (source_version)]
521 [target_version_index (target_version)];
522 if (!resultp->tested)
524 /* Canonicalize source_version and target_version, for easier
525 arithmetic. */
526 int try_source_version = 6 + source_version_index (source_version);
527 int try_target_version = 6 + target_version_index (target_version);
528 /* Sanity check. */
529 if (try_source_version <= try_target_version)
531 /* Try $JAVAC. */
532 struct temp_dir *tmpdir;
533 char *conftest_file_name;
534 char *compiled_file_name;
535 const char *java_sources[1];
536 const char *nowarn_option;
537 struct stat statbuf;
539 tmpdir = create_temp_dir ("java", NULL, false);
540 if (tmpdir == NULL)
541 return true;
543 conftest_file_name =
544 xconcatenated_filename (tmpdir->dir_name, "conftest.java", NULL);
545 if (write_temp_file (tmpdir, conftest_file_name, "class conftest {}"))
547 free (conftest_file_name);
548 cleanup_temp_dir (tmpdir);
549 return true;
552 compiled_file_name =
553 xconcatenated_filename (tmpdir->dir_name, "conftest.class", NULL);
554 register_temp_file (tmpdir, compiled_file_name);
556 /* See the discussion in javacomp.m4. */
557 nowarn_option = " -Xlint:-options";
558 char *javac_nowarn = xasprintf ("%s%s", javac, nowarn_option);
559 assume (javac_nowarn != NULL);
561 java_sources[0] = conftest_file_name;
562 if ((!compile_using_envjavac (javac_nowarn,
563 java_sources, 1, tmpdir->dir_name,
564 false, false, false, true)
565 && stat (compiled_file_name, &statbuf) >= 0)
566 || (nowarn_option = "",
567 unlink (compiled_file_name),
568 (!compile_using_envjavac (javac,
569 java_sources, 1, tmpdir->dir_name,
570 false, false, false, true)
571 && stat (compiled_file_name, &statbuf) >= 0)))
573 /* $JAVAC compiled conftest.java successfully. */
574 int compiler_cfversion =
575 get_classfile_version (compiled_file_name);
576 int compiler_target_version = compiler_cfversion - 44;
578 /* It is hard to determine the compiler_source_version. This
579 would require a list of code snippets that can be compiled only
580 with a specific '-source' option and up, and this list would
581 need to grow every 6 months.
582 Also, $JAVAC may already include a '-source' option.
583 Therefore, pass a '-source' option always. */
584 char source_option[30];
585 sprintf (source_option, " -source %s%d",
586 try_source_version <= 8 ? "1." : "",
587 try_source_version);
589 /* And pass a '-target' option as well, if needed.
590 (All supported javac versions support both, see the table in
591 javacomp.m4.) */
592 char target_option[30];
593 if (try_target_version == compiler_target_version)
594 target_option[0] = '\0';
595 else
596 sprintf (target_option, " -target %s%d",
597 try_target_version <= 8 ? "1." : "",
598 try_target_version);
600 char *javac_source_target =
601 xasprintf ("%s%s%s%s", javac, nowarn_option,
602 source_option, target_option);
603 assume (javac_source_target != NULL);
605 unlink (compiled_file_name);
607 java_sources[0] = conftest_file_name;
608 if (!compile_using_envjavac (javac_source_target,
609 java_sources, 1, tmpdir->dir_name,
610 false, false, false, true)
611 && stat (compiled_file_name, &statbuf) >= 0)
613 /* The compiler directly supports the desired source_version
614 and target_version. Perfect. */
615 free (javac_source_target);
617 resultp->nowarn_option = (nowarn_option[0] != '\0');
618 resultp->source_option = try_source_version;
619 resultp->target_option =
620 (try_target_version == compiler_target_version ? 0 :
621 try_target_version);
622 resultp->usable = true;
624 else
626 /* If the desired source_version or target_version were too
627 large for the compiler, there's nothing else we can do. */
628 unsigned int compiler_version;
630 free (javac_source_target);
633 size_t command_length;
634 char *command;
635 const char *argv[4];
637 command_length = strlen (javac) + 9 + 1;
639 command = (char *) xmalloca (command_length);
641 char *p = command;
642 p = stpcpy (p, javac);
643 p = stpcpy (p, " -version");
644 *p++ = '\0';
645 /* Ensure command_length was correctly calculated. */
646 if (p - command > command_length)
647 abort ();
650 argv[0] = BOURNE_SHELL;
651 argv[1] = "-c";
652 argv[2] = command;
653 argv[3] = NULL;
654 compiler_version =
655 get_compiler_version (javac, BOURNE_SHELL, argv);
657 freea (command);
660 if (try_source_version <= compiler_version
661 && try_target_version <= compiler_version)
663 /* Increase try_source_version and compiler_version until
664 the compiler accepts these values. This is necessary
665 to make e.g. try_source_version = 6 work with Java 12
666 or newer, or try_source_version = 7 work with Java 20
667 or newer. */
668 for (;;)
670 /* Invariant:
671 try_source_version <= try_target_version. */
672 if (try_source_version == try_target_version)
673 try_target_version++;
674 try_source_version++;
675 if (try_source_version > compiler_version)
676 break;
678 sprintf (source_option, " -source %s%d",
679 try_source_version <= 8 ? "1." : "",
680 try_source_version);
682 if (try_target_version == compiler_target_version)
683 target_option[0] = '\0';
684 else
685 sprintf (target_option, " -target %s%d",
686 try_target_version <= 8 ? "1." : "",
687 try_target_version);
689 javac_source_target =
690 xasprintf ("%s%s%s%s", javac, nowarn_option,
691 source_option, target_option);
692 assume (javac_source_target != NULL);
694 unlink (compiled_file_name);
696 java_sources[0] = conftest_file_name;
697 if (!compile_using_envjavac (javac_source_target,
698 java_sources, 1,
699 tmpdir->dir_name,
700 false, false,
701 false, true)
702 && stat (compiled_file_name, &statbuf) >= 0)
704 /* The compiler supports the try_source_version
705 and try_target_version. It's better than
706 nothing. */
707 free (javac_source_target);
709 resultp->nowarn_option = (nowarn_option[0] != '\0');
710 resultp->source_option = try_source_version;
711 resultp->target_option =
712 (try_target_version == compiler_target_version ? 0 :
713 try_target_version);
714 resultp->usable = true;
715 break;
718 free (javac_source_target);
724 cleanup_temp_dir (tmpdir);
726 free (javac_nowarn);
727 free (compiled_file_name);
728 free (conftest_file_name);
731 resultp->tested = true;
734 *usablep = resultp->usable;
735 if (resultp->nowarn_option)
736 strcpy (nowarn_option_out, " -Xlint:-options");
737 else
738 nowarn_option_out[0] = '\0';
739 sprintf (source_option_out, " -source %s%d",
740 resultp->source_option <= 8 ? "1." : "",
741 resultp->source_option);
742 if (resultp->target_option == 0)
743 target_option_out[0] = '\0';
744 else
745 sprintf (target_option_out, " -target %s%d",
746 resultp->target_option <= 8 ? "1." : "",
747 resultp->target_option);
748 return false;
751 static bool
752 is_javac_present (void)
754 static bool javac_tested;
755 static bool javac_present;
757 if (!javac_tested)
759 /* Test for presence of javac: "javac 2> /dev/null ; test $? -le 2" */
760 const char *argv[2];
761 int exitstatus;
763 argv[0] = "javac";
764 argv[1] = NULL;
765 exitstatus = execute ("javac", "javac", argv, NULL, NULL,
766 false, false, true, true,
767 true, false, NULL);
768 javac_present = (exitstatus == 0 || exitstatus == 1 || exitstatus == 2);
769 javac_tested = true;
772 return javac_present;
775 /* Test whether javac can be used and whether it needs a -source and/or
776 -target option, as well as an option to inhibit warnings.
777 Return a failure indicator (true upon error). */
778 static bool
779 is_javac_usable (const char *source_version, const char *target_version,
780 bool *usablep,
781 char nowarn_option_out[17],
782 char source_option_out[20], char target_option_out[20])
784 /* The cache depends on the source_version and target_version. */
785 struct result_t
787 /*bool*/ unsigned int tested : 1;
788 /*bool*/ unsigned int usable : 1;
789 /*bool*/ unsigned int nowarn_option : 1;
790 unsigned int source_option : 7;
791 unsigned int target_option : 7;
793 static struct result_t result_cache[SOURCE_VERSION_BOUND][TARGET_VERSION_BOUND];
794 struct result_t *resultp;
796 resultp = &result_cache[source_version_index (source_version)]
797 [target_version_index (target_version)];
798 if (!resultp->tested)
800 /* Canonicalize source_version and target_version, for easier
801 arithmetic. */
802 int try_source_version = 6 + source_version_index (source_version);
803 int try_target_version = 6 + target_version_index (target_version);
804 /* Sanity check. */
805 if (try_source_version <= try_target_version)
807 /* Try javac. */
808 struct temp_dir *tmpdir;
809 char *conftest_file_name;
810 char *compiled_file_name;
811 const char *java_sources[1];
812 const char *nowarn_option;
813 struct stat statbuf;
815 tmpdir = create_temp_dir ("java", NULL, false);
816 if (tmpdir == NULL)
817 return true;
819 conftest_file_name =
820 xconcatenated_filename (tmpdir->dir_name, "conftest.java", NULL);
821 if (write_temp_file (tmpdir, conftest_file_name, "class conftest {}"))
823 free (conftest_file_name);
824 cleanup_temp_dir (tmpdir);
825 return true;
828 compiled_file_name =
829 xconcatenated_filename (tmpdir->dir_name, "conftest.class", NULL);
830 register_temp_file (tmpdir, compiled_file_name);
832 /* See the discussion in javacomp.m4. */
833 nowarn_option = "-Xlint:-options";
835 java_sources[0] = conftest_file_name;
836 if ((!compile_using_javac (java_sources, 1,
837 nowarn_option,
838 false, source_version,
839 false, target_version,
840 tmpdir->dir_name,
841 false, false, false, true)
842 && stat (compiled_file_name, &statbuf) >= 0)
843 || (nowarn_option = NULL,
844 unlink (compiled_file_name),
845 (!compile_using_javac (java_sources, 1,
846 nowarn_option,
847 false, source_version,
848 false, target_version,
849 tmpdir->dir_name,
850 false, false, false, true)
851 && stat (compiled_file_name, &statbuf) >= 0)))
853 /* javac compiled conftest.java successfully. */
854 int compiler_cfversion =
855 get_classfile_version (compiled_file_name);
856 int compiler_target_version = compiler_cfversion - 44;
858 /* It is hard to determine the compiler_source_version. This
859 would require a list of code snippets that can be compiled only
860 with a specific '-source' option and up, and this list would
861 need to grow every 6 months.
862 Also, javac may point to a shell script that already includes a
863 '-source' option.
864 Therefore, pass a '-source' option always. */
865 char source_option[20];
866 sprintf (source_option, "%s%d",
867 try_source_version <= 8 ? "1." : "",
868 try_source_version);
870 /* And pass a '-target' option as well, if needed.
871 (All supported javac versions support both, see the table in
872 javacomp.m4.) */
873 char target_option[20];
874 sprintf (target_option, "%s%d",
875 try_target_version <= 8 ? "1." : "",
876 try_target_version);
878 unlink (compiled_file_name);
880 java_sources[0] = conftest_file_name;
881 if (!compile_using_javac (java_sources, 1,
882 nowarn_option,
883 true,
884 source_option,
885 try_target_version != compiler_target_version,
886 target_option,
887 tmpdir->dir_name,
888 false, false, false, true)
889 && stat (compiled_file_name, &statbuf) >= 0)
891 /* The compiler directly supports the desired source_version
892 and target_version. Perfect. */
893 resultp->nowarn_option = (nowarn_option != NULL);
894 resultp->source_option = try_source_version;
895 resultp->target_option =
896 (try_target_version == compiler_target_version ? 0 :
897 try_target_version);
898 resultp->usable = true;
900 else
902 /* If the desired source_version or target_version were too
903 large for the compiler, there's nothing else we can do. */
904 unsigned int compiler_version;
907 const char *argv[3];
909 argv[0] = "javac";
910 argv[1] = "-version";
911 argv[2] = NULL;
912 compiler_version =
913 get_compiler_version ("javac", argv[0], argv);
916 if (try_source_version <= compiler_version
917 && try_target_version <= compiler_version)
919 /* Increase try_source_version and compiler_version until
920 the compiler accepts these values. This is necessary
921 to make e.g. try_source_version = 6 work with Java 12
922 or newer, or try_source_version = 7 work with Java 20
923 or newer. */
924 for (;;)
926 /* Invariant:
927 try_source_version <= try_target_version. */
928 if (try_source_version == try_target_version)
929 try_target_version++;
930 try_source_version++;
931 if (try_source_version > compiler_version)
932 break;
934 sprintf (source_option, "%s%d",
935 try_source_version <= 8 ? "1." : "",
936 try_source_version);
938 sprintf (target_option, "%s%d",
939 try_target_version <= 8 ? "1." : "",
940 try_target_version);
942 unlink (compiled_file_name);
944 java_sources[0] = conftest_file_name;
945 if (!compile_using_javac (java_sources, 1,
946 nowarn_option,
947 true,
948 source_option,
949 try_target_version != compiler_target_version,
950 target_option,
951 tmpdir->dir_name,
952 false, false, false, true)
953 && stat (compiled_file_name, &statbuf) >= 0)
955 /* The compiler supports the try_source_version
956 and try_target_version. It's better than
957 nothing. */
958 resultp->nowarn_option = (nowarn_option != NULL);
959 resultp->source_option = try_source_version;
960 resultp->target_option =
961 (try_target_version == compiler_target_version ? 0 :
962 try_target_version);
963 resultp->usable = true;
964 break;
971 cleanup_temp_dir (tmpdir);
973 free (compiled_file_name);
974 free (conftest_file_name);
977 resultp->tested = true;
980 *usablep = resultp->usable;
981 if (resultp->nowarn_option)
982 strcpy (nowarn_option_out, "-Xlint:-options");
983 else
984 nowarn_option_out[0] = '\0';
985 sprintf (source_option_out, "%s%d",
986 resultp->source_option <= 8 ? "1." : "",
987 resultp->source_option);
988 if (resultp->target_option == 0)
989 target_option_out[0] = '\0';
990 else
991 sprintf (target_option_out, "%s%d",
992 resultp->target_option <= 8 ? "1." : "",
993 resultp->target_option);
994 return false;
997 /* ============================= Main function ============================= */
999 bool
1000 compile_java_class (const char * const *java_sources,
1001 unsigned int java_sources_count,
1002 const char * const *classpaths,
1003 unsigned int classpaths_count,
1004 const char *source_version,
1005 const char *target_version,
1006 const char *directory,
1007 bool optimize, bool debug,
1008 bool use_minimal_classpath,
1009 bool verbose)
1011 bool err = false;
1012 char *old_JAVA_HOME;
1014 /* Map source_version 1.1 ... 1.5 to 1.6. */
1015 if (source_version[0] == '1' && source_version[1] == '.'
1016 && (source_version[2] >= '1' && source_version[2] <= '5')
1017 && source_version[3] == '\0')
1018 source_version = "1.6";
1020 /* Map target_version 1.1 ... 1.5 to 1.6. */
1021 if (target_version != NULL
1022 && target_version[0] == '1' && target_version[1] == '.'
1023 && (target_version[2] >= '1' && target_version[2] <= '5')
1024 && target_version[3] == '\0')
1025 target_version = "1.6";
1028 const char *javac = getenv ("JAVAC");
1029 if (javac != NULL && javac[0] != '\0')
1031 bool usable = false;
1032 char nowarn_option[17];
1033 char source_option[30];
1034 char target_option[30];
1036 if (target_version == NULL)
1037 target_version = default_target_version ();
1039 if (is_envjavac_usable (javac,
1040 source_version, target_version,
1041 &usable,
1042 nowarn_option,
1043 source_option, target_option))
1045 err = true;
1046 goto done1;
1049 if (usable)
1051 char *old_classpath;
1052 char *javac_with_options;
1054 /* Set CLASSPATH. */
1055 old_classpath =
1056 set_classpath (classpaths, classpaths_count, false, verbose);
1058 javac_with_options =
1059 xasprintf ("%s%s%s%s", javac,
1060 nowarn_option, source_option, target_option);
1061 assume (javac_with_options != NULL);
1063 err = compile_using_envjavac (javac_with_options,
1064 java_sources, java_sources_count,
1065 directory, optimize, debug, verbose,
1066 false);
1068 free (javac_with_options);
1070 /* Reset CLASSPATH. */
1071 reset_classpath (old_classpath);
1073 goto done1;
1078 /* Unset the JAVA_HOME environment variable. */
1079 old_JAVA_HOME = getenv ("JAVA_HOME");
1080 if (old_JAVA_HOME != NULL)
1082 old_JAVA_HOME = xstrdup (old_JAVA_HOME);
1083 unsetenv ("JAVA_HOME");
1086 if (is_javac_present ())
1088 bool usable = false;
1089 char nowarn_option[17];
1090 char source_option[20];
1091 char target_option[20];
1093 if (target_version == NULL)
1094 target_version = default_target_version ();
1096 if (is_javac_usable (source_version, target_version,
1097 &usable,
1098 nowarn_option,
1099 source_option, target_option))
1101 err = true;
1102 goto done1;
1105 if (usable)
1107 char *old_classpath;
1109 /* Set CLASSPATH. We don't use the "-classpath ..." option because
1110 in JDK 1.1.x its argument should also contain the JDK's
1111 classes.zip, but we don't know its location. (In JDK 1.3.0 it
1112 would work.) */
1113 old_classpath =
1114 set_classpath (classpaths, classpaths_count, use_minimal_classpath,
1115 verbose);
1117 err = compile_using_javac (java_sources, java_sources_count,
1118 nowarn_option[0] != '\0' ? nowarn_option : NULL,
1119 true, source_option,
1120 target_option[0] != '\0', target_option,
1121 directory, optimize, debug, verbose,
1122 false);
1124 /* Reset CLASSPATH. */
1125 reset_classpath (old_classpath);
1127 goto done2;
1131 error (0, 0, _("Java compiler not found, try setting $JAVAC"));
1132 err = true;
1134 done2:
1135 if (old_JAVA_HOME != NULL)
1137 xsetenv ("JAVA_HOME", old_JAVA_HOME, 1);
1138 free (old_JAVA_HOME);
1141 done1:
1142 return err;