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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 81bool 82compile_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} 480