* lib/wait-process.c, lib/wait-process.h, lib/csharpcomp.c,
[gnulib.git] / lib / javacomp.c
1 /* Compile a Java program.
2    Copyright (C) 2001-2003 Free Software Foundation, Inc.
3    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
4
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 2, or (at your option)
8    any later version.
9
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.
14
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software Foundation,
17    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
18
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22 #include <alloca.h>
23
24 /* Specification.  */
25 #include "javacomp.h"
26
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include "execute.h"
32 #include "pipe.h"
33 #include "wait-process.h"
34 #include "classpath.h"
35 #include "xsetenv.h"
36 #include "sh-quote.h"
37 #include "safe-read.h"
38 #include "xalloc.h"
39 #include "xallocsa.h"
40 #include "error.h"
41 #include "gettext.h"
42
43 #define _(str) gettext (str)
44
45
46 /* Survey of Java compilers.
47
48    A = does it work without CLASSPATH being set
49    C = option to set CLASSPATH, other than setting it in the environment
50    O = option for optimizing
51    g = option for debugging
52    T = test for presence
53
54    Program  from        A  C               O  g  T
55
56    $JAVAC   unknown     N  n/a            -O -g  true
57    gcj -C   GCC 3.2     Y  --classpath=P  -O -g  gcj --version | sed -e 's,^[^0-9]*,,' -e 1q | sed -e '/^3\.[01]/d' | grep '^[3-9]' >/dev/null
58    javac    JDK 1.1.8   Y  -classpath P   -O -g  javac 2>/dev/null; test $? = 1
59    javac    JDK 1.3.0   Y  -classpath P   -O -g  javac 2>/dev/null; test $? -le 2
60    jikes    Jikes 1.14  N  -classpath P   -O -g  jikes 2>/dev/null; test $? = 1
61
62    All compilers support the option "-d DIRECTORY" for the base directory
63    of the classes to be written.
64
65    The CLASSPATH is a colon separated list of pathnames. (On Windows: a
66    semicolon separated list of pathnames.)
67
68    We try the Java compilers in the following order:
69      1. getenv ("JAVAC"), because the user must be able to override our
70         preferences,
71      2. "gcj -C", because it is a completely free compiler,
72      3. "javac", because it is a standard compiler,
73      4. "jikes", comes last because it has some deviating interpretation
74         of the Java Language Specification and because it requires a
75         CLASSPATH environment variable.
76
77    We unset the JAVA_HOME environment variable, because a wrong setting of
78    this variable can confuse the JDK's javac.
79  */
80
81 bool
82 compile_java_class (const char * const *java_sources,
83                     unsigned int java_sources_count,
84                     const char * const *classpaths,
85                     unsigned int classpaths_count,
86                     const char *directory,
87                     bool optimize, bool debug,
88                     bool use_minimal_classpath,
89                     bool verbose)
90 {
91   bool err = false;
92   char *old_JAVA_HOME;
93
94   {
95     const char *javac = getenv ("JAVAC");
96     if (javac != NULL && javac[0] != '\0')
97       {
98         /* Because $JAVAC may consist of a command and options, we use the
99            shell.  Because $JAVAC has been set by the user, we leave all
100            all environment variables in place, including JAVA_HOME, and
101            we don't erase the user's CLASSPATH.  */
102         char *old_classpath;
103         unsigned int command_length;
104         char *command;
105         char *argv[4];
106         int exitstatus;
107         unsigned int i;
108         char *p;
109
110         /* Set CLASSPATH.  */
111         old_classpath =
112           set_classpath (classpaths, classpaths_count, false,
113                          verbose);
114
115         command_length = strlen (javac);
116         if (optimize)
117           command_length += 3;
118         if (debug)
119           command_length += 3;
120         if (directory != NULL)
121           command_length += 4 + shell_quote_length (directory);
122         for (i = 0; i < java_sources_count; i++)
123           command_length += 1 + shell_quote_length (java_sources[i]);
124         command_length += 1;
125
126         command = (char *) xallocsa (command_length);
127         p = command;
128         /* Don't shell_quote $JAVAC, because it may consist of a command
129            and options.  */
130         memcpy (p, javac, strlen (javac));
131         p += strlen (javac);
132         if (optimize)
133           {
134             memcpy (p, " -O", 3);
135             p += 3;
136           }
137         if (debug)
138           {
139             memcpy (p, " -g", 3);
140             p += 3;
141           }
142         if (directory != NULL)
143           {
144             memcpy (p, " -d ", 4);
145             p += 4;
146             p = shell_quote_copy (p, directory);
147           }
148         for (i = 0; i < java_sources_count; i++)
149           {
150             *p++ = ' ';
151             p = shell_quote_copy (p, java_sources[i]);
152           }
153         *p++ = '\0';
154         /* Ensure command_length was correctly calculated.  */
155         if (p - command > command_length)
156           abort ();
157
158         if (verbose)
159           printf ("%s\n", command);
160
161         argv[0] = "/bin/sh";
162         argv[1] = "-c";
163         argv[2] = command;
164         argv[3] = NULL;
165         exitstatus = execute (javac, "/bin/sh", argv, false, false, false,
166                               false, true, true);
167         err = (exitstatus != 0);
168
169         freesa (command);
170
171         /* Reset CLASSPATH.  */
172         reset_classpath (old_classpath);
173
174         goto done1;
175       }
176   }
177
178   /* Unset the JAVA_HOME environment variable.  */
179   old_JAVA_HOME = getenv ("JAVA_HOME");
180   if (old_JAVA_HOME != NULL)
181     {
182       old_JAVA_HOME = xstrdup (old_JAVA_HOME);
183       unsetenv ("JAVA_HOME");
184     }
185
186   {
187     static bool gcj_tested;
188     static bool gcj_present;
189
190     if (!gcj_tested)
191       {
192         /* Test for presence of gcj:
193            "gcj --version 2> /dev/null | \
194             sed -e 's,^[^0-9]*,,' -e 1q | \
195             sed -e '/^3\.[01]/d' | grep '^[3-9]' > /dev/null"  */
196         char *argv[3];
197         pid_t child;
198         int fd[1];
199         int exitstatus;
200
201         argv[0] = "gcj";
202         argv[1] = "--version";
203         argv[2] = NULL;
204         child = create_pipe_in ("gcj", "gcj", argv, DEV_NULL, true, true,
205                                 false, fd);
206         gcj_present = false;
207         if (child != -1)
208           {
209             /* Read the subprocess output, drop all lines except the first,
210                drop all characters before the first digit, and test whether
211                the remaining string starts with a digit >= 3, but not with
212                "3.0" or "3.1".  */
213             char c[3];
214             size_t count = 0;
215
216             while (safe_read (fd[0], &c[count], 1) > 0)
217               {
218                 if (c[count] == '\n')
219                   break;
220                 if (count == 0)
221                   {
222                     if (!(c[0] >= '0' && c[0] <= '9'))
223                       continue;
224                     gcj_present = (c[0] >= '3');
225                   }
226                 count++;
227                 if (count == 3)
228                   {
229                     if (c[0] == '3' && c[1] == '.'
230                         && (c[2] == '0' || c[2] == '1'))
231                       gcj_present = false;
232                     break;
233                   }
234               }
235             while (safe_read (fd[0], &c[0], 1) > 0)
236               ;
237
238             close (fd[0]);
239
240             /* Remove zombie process from process list, and retrieve exit
241                status.  */
242             exitstatus =
243               wait_subprocess (child, "gcj", false, true, true, false);
244             if (exitstatus != 0)
245               gcj_present = false;
246           }
247         gcj_tested = true;
248       }
249
250     if (gcj_present)
251       {
252         char *old_classpath;
253         unsigned int argc;
254         char **argv;
255         char **argp;
256         int exitstatus;
257         unsigned int i;
258
259         /* Set CLASSPATH.  We could also use the --CLASSPATH=... option
260            of gcj.  Note that --classpath=... option is different: its
261            argument should also contain gcj's libgcj.jar, but we don't
262            know its location.  */
263         old_classpath =
264           set_classpath (classpaths, classpaths_count, use_minimal_classpath,
265                          verbose);
266
267         argc =
268           2 + (optimize ? 1 : 0) + (debug ? 1 : 0) + (directory != NULL ? 2 : 0)
269           + java_sources_count;
270         argv = (char **) xallocsa ((argc + 1) * sizeof (char *));
271
272         argp = argv;
273         *argp++ = "gcj";
274         *argp++ = "-C";
275         if (optimize)
276           *argp++ = "-O";
277         if (debug)
278           *argp++ = "-g";
279         if (directory != NULL)
280           {
281             *argp++ = "-d";
282             *argp++ = (char *) directory;
283           }
284         for (i = 0; i < java_sources_count; i++)
285           *argp++ = (char *) java_sources[i];
286         *argp = NULL;
287         /* Ensure argv length was correctly calculated.  */
288         if (argp - argv != argc)
289           abort ();
290
291         if (verbose)
292           {
293             char *command = shell_quote_argv (argv);
294             printf ("%s\n", command);
295             free (command);
296           }
297
298         exitstatus = execute ("gcj", "gcj", argv, false, false, false, false,
299                               true, true);
300         err = (exitstatus != 0);
301
302         freesa (argv);
303
304         /* Reset CLASSPATH.  */
305         reset_classpath (old_classpath);
306
307         goto done2;
308       }
309   }
310
311   {
312     static bool javac_tested;
313     static bool javac_present;
314
315     if (!javac_tested)
316       {
317         /* Test for presence of javac: "javac 2> /dev/null ; test $? -le 2"  */
318         char *argv[2];
319         int exitstatus;
320
321         argv[0] = "javac";
322         argv[1] = NULL;
323         exitstatus = execute ("javac", "javac", argv, false, false, true, true,
324                               true, false);
325         javac_present = (exitstatus == 0 || exitstatus == 1 || exitstatus == 2);
326         javac_tested = true;
327       }
328
329     if (javac_present)
330       {
331         char *old_classpath;
332         unsigned int argc;
333         char **argv;
334         char **argp;
335         int exitstatus;
336         unsigned int i;
337
338         /* Set CLASSPATH.  We don't use the "-classpath ..." option because
339            in JDK 1.1.x its argument should also contain the JDK's classes.zip,
340            but we don't know its location.  (In JDK 1.3.0 it would work.)  */
341         old_classpath =
342           set_classpath (classpaths, classpaths_count, use_minimal_classpath,
343                          verbose);
344
345         argc =
346           1 + (optimize ? 1 : 0) + (debug ? 1 : 0) + (directory != NULL ? 2 : 0)
347           + java_sources_count;
348         argv = (char **) xallocsa ((argc + 1) * sizeof (char *));
349
350         argp = argv;
351         *argp++ = "javac";
352         if (optimize)
353           *argp++ = "-O";
354         if (debug)
355           *argp++ = "-g";
356         if (directory != NULL)
357           {
358             *argp++ = "-d";
359             *argp++ = (char *) directory;
360           }
361         for (i = 0; i < java_sources_count; i++)
362           *argp++ = (char *) java_sources[i];
363         *argp = NULL;
364         /* Ensure argv length was correctly calculated.  */
365         if (argp - argv != argc)
366           abort ();
367
368         if (verbose)
369           {
370             char *command = shell_quote_argv (argv);
371             printf ("%s\n", command);
372             free (command);
373           }
374
375         exitstatus = execute ("javac", "javac", argv, false, false, false,
376                               false, true, true);
377         err = (exitstatus != 0);
378
379         freesa (argv);
380
381         /* Reset CLASSPATH.  */
382         reset_classpath (old_classpath);
383
384         goto done2;
385       }
386   }
387
388   {
389     static bool jikes_tested;
390     static bool jikes_present;
391
392     if (!jikes_tested)
393       {
394         /* Test for presence of jikes: "jikes 2> /dev/null ; test $? = 1"  */
395         char *argv[2];
396         int exitstatus;
397
398         argv[0] = "jikes";
399         argv[1] = NULL;
400         exitstatus = execute ("jikes", "jikes", argv, false, false, true, true,
401                               true, false);
402         jikes_present = (exitstatus == 0 || exitstatus == 1);
403         jikes_tested = true;
404       }
405
406     if (jikes_present)
407       {
408         char *old_classpath;
409         unsigned int argc;
410         char **argv;
411         char **argp;
412         int exitstatus;
413         unsigned int i;
414
415         /* Set CLASSPATH.  We could also use the "-classpath ..." option.
416            Since jikes doesn't come with its own standard library, it
417            needs a classes.zip or rt.jar or libgcj.jar in the CLASSPATH.
418            To increase the chance of success, we reuse the current CLASSPATH
419            if the user has set it.  */
420         old_classpath =
421           set_classpath (classpaths, classpaths_count, false,
422                          verbose);
423
424         argc =
425           1 + (optimize ? 1 : 0) + (debug ? 1 : 0) + (directory != NULL ? 2 : 0)
426           + java_sources_count;
427         argv = (char **) xallocsa ((argc + 1) * sizeof (char *));
428
429         argp = argv;
430         *argp++ = "jikes";
431         if (optimize)
432           *argp++ = "-O";
433         if (debug)
434           *argp++ = "-g";
435         if (directory != NULL)
436           {
437             *argp++ = "-d";
438             *argp++ = (char *) directory;
439           }
440         for (i = 0; i < java_sources_count; i++)
441           *argp++ = (char *) java_sources[i];
442         *argp = NULL;
443         /* Ensure argv length was correctly calculated.  */
444         if (argp - argv != argc)
445           abort ();
446
447         if (verbose)
448           {
449             char *command = shell_quote_argv (argv);
450             printf ("%s\n", command);
451             free (command);
452           }
453
454         exitstatus = execute ("jikes", "jikes", argv, false, false, false,
455                               false, true, true);
456         err = (exitstatus != 0);
457
458         freesa (argv);
459
460         /* Reset CLASSPATH.  */
461         reset_classpath (old_classpath);
462
463         goto done2;
464       }
465   }
466
467   error (0, 0, _("Java compiler not found, try installing gcj or set $JAVAC"));
468   err = true;
469
470  done2:
471   if (old_JAVA_HOME != NULL)
472     {
473       xsetenv ("JAVA_HOME", old_JAVA_HOME, 1);
474       free (old_JAVA_HOME);
475     }
476
477  done1:
478   return err;
479 }