1/* Compile a C# program. 2 Copyright (C) 2003-2006 Free Software Foundation, Inc. 3 Written by Bruno Haible <bruno@clisp.org>, 2003. 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#include <config.h> 20#include <alloca.h> 21 22/* Specification. */ 23#include "csharpcomp.h" 24 25#include <errno.h> 26#include <stdio.h> 27#include <stdlib.h> 28#include <string.h> 29 30#include "execute.h" 31#include "pipe.h" 32#include "wait-process.h" 33#include "getline.h" 34#include "sh-quote.h" 35#include "safe-read.h" 36#include "xallocsa.h" 37#include "error.h" 38#include "gettext.h" 39 40#define _(str) gettext (str) 41 42 43/* Survey of C# compilers. 44 45 Program from 46 47 cscc pnet 48 mcs mono 49 csc sscli 50 51 We try the CIL interpreters in the following order: 52 1. "cscc", because it is a completely free system. 53 2. "mcs", because it is a free system but doesn't integrate so well 54 with Unix. (Command line options start with / instead of -. Errors go 55 to stdout instead of stderr. Source references are printed as 56 "file(lineno)" instead of "file:lineno:".) 57 3. "csc", although it is not free, because it is a kind of "reference 58 implementation" of C#. 59 But the order can be changed through the --enable-csharp configuration 60 option. 61 */ 62 63static int 64compile_csharp_using_pnet (const char * const *sources, 65 unsigned int sources_count, 66 const char * const *libdirs, 67 unsigned int libdirs_count, 68 const char * const *libraries, 69 unsigned int libraries_count, 70 const char *output_file, bool output_is_library, 71 bool optimize, bool debug, 72 bool verbose) 73{ 74 static bool cscc_tested; 75 static bool cscc_present; 76 77 if (!cscc_tested) 78 { 79 /* Test for presence of cscc: 80 "cscc --version >/dev/null 2>/dev/null" */ 81 char *argv[3]; 82 int exitstatus; 83 84 argv[0] = "cscc"; 85 argv[1] = "--version"; 86 argv[2] = NULL; 87 exitstatus = execute ("cscc", "cscc", argv, false, false, true, true, 88 true, false); 89 cscc_present = (exitstatus == 0); 90 cscc_tested = true; 91 } 92 93 if (cscc_present) 94 { 95 unsigned int argc; 96 char **argv; 97 char **argp; 98 int exitstatus; 99 unsigned int i; 100 101 argc = 102 1 + (output_is_library ? 1 : 0) + 2 + 2 * libdirs_count 103 + 2 * libraries_count + (optimize ? 1 : 0) + (debug ? 1 : 0) 104 + sources_count; 105 argv = (char **) xallocsa ((argc + 1) * sizeof (char *)); 106 107 argp = argv; 108 *argp++ = "cscc"; 109 if (output_is_library) 110 *argp++ = "-shared"; 111 *argp++ = "-o"; 112 *argp++ = (char *) output_file; 113 for (i = 0; i < libdirs_count; i++) 114 { 115 *argp++ = "-L"; 116 *argp++ = (char *) libdirs[i]; 117 } 118 for (i = 0; i < libraries_count; i++) 119 { 120 *argp++ = "-l"; 121 *argp++ = (char *) libraries[i]; 122 } 123 if (optimize) 124 *argp++ = "-O"; 125 if (debug) 126 *argp++ = "-g"; 127 for (i = 0; i < sources_count; i++) 128 { 129 const char *source_file = sources[i]; 130 if (strlen (source_file) >= 10 131 && memcmp (source_file + strlen (source_file) - 10, ".resources", 132 10) == 0) 133 { 134 char *option = (char *) xallocsa (12 + strlen (source_file) + 1); 135 136 memcpy (option, "-fresources=", 12); 137 strcpy (option + 12, source_file); 138 *argp++ = option; 139 } 140 else 141 *argp++ = (char *) source_file; 142 } 143 *argp = NULL; 144 /* Ensure argv length was correctly calculated. */ 145 if (argp - argv != argc) 146 abort (); 147 148 if (verbose) 149 { 150 char *command = shell_quote_argv (argv); 151 printf ("%s\n", command); 152 free (command); 153 } 154 155 exitstatus = execute ("cscc", "cscc", argv, false, false, false, false, 156 true, true); 157 158 for (i = 0; i < sources_count; i++) 159 if (argv[argc - sources_count + i] != sources[i]) 160 freesa (argv[argc - sources_count + i]); 161 freesa (argv); 162 163 return (exitstatus != 0); 164 } 165 else 166 return -1; 167} 168 169static int 170compile_csharp_using_mono (const char * const *sources, 171 unsigned int sources_count, 172 const char * const *libdirs, 173 unsigned int libdirs_count, 174 const char * const *libraries, 175 unsigned int libraries_count, 176 const char *output_file, bool output_is_library, 177 bool optimize, bool debug, 178 bool verbose) 179{ 180 static bool mcs_tested; 181 static bool mcs_present; 182 183 if (!mcs_tested) 184 { 185 /* Test for presence of mcs: 186 "mcs --version >/dev/null 2>/dev/null" */ 187 char *argv[3]; 188 int exitstatus; 189 190 argv[0] = "mcs"; 191 argv[1] = "--version"; 192 argv[2] = NULL; 193 exitstatus = execute ("mcs", "mcs", argv, false, false, true, true, true, 194 false); 195 mcs_present = (exitstatus == 0); 196 mcs_tested = true; 197 } 198 199 if (mcs_present) 200 { 201 unsigned int argc; 202 char **argv; 203 char **argp; 204 pid_t child; 205 int fd[1]; 206 FILE *fp; 207 char *line[2]; 208 size_t linesize[2]; 209 size_t linelen[2]; 210 unsigned int l; 211 int exitstatus; 212 unsigned int i; 213 214 argc = 215 1 + (output_is_library ? 1 : 0) + 1 + libdirs_count + libraries_count 216 + (debug ? 1 : 0) + sources_count; 217 argv = (char **) xallocsa ((argc + 1) * sizeof (char *)); 218 219 argp = argv; 220 *argp++ = "mcs"; 221 if (output_is_library) 222 *argp++ = "-target:library"; 223 { 224 char *option = (char *) xallocsa (5 + strlen (output_file) + 1); 225 memcpy (option, "-out:", 5); 226 strcpy (option + 5, output_file); 227 *argp++ = option; 228 } 229 for (i = 0; i < libdirs_count; i++) 230 { 231 char *option = (char *) xallocsa (5 + strlen (libdirs[i]) + 1); 232 memcpy (option, "-lib:", 5); 233 strcpy (option + 5, libdirs[i]); 234 *argp++ = option; 235 } 236 for (i = 0; i < libraries_count; i++) 237 { 238 char *option = (char *) xallocsa (11 + strlen (libraries[i]) + 4 + 1); 239 memcpy (option, "-reference:", 11); 240 memcpy (option + 11, libraries[i], strlen (libraries[i])); 241 strcpy (option + 11 + strlen (libraries[i]), ".dll"); 242 *argp++ = option; 243 } 244 if (debug) 245 *argp++ = "-debug"; 246 for (i = 0; i < sources_count; i++) 247 { 248 const char *source_file = sources[i]; 249 if (strlen (source_file) >= 10 250 && memcmp (source_file + strlen (source_file) - 10, ".resources", 251 10) == 0) 252 { 253 char *option = (char *) xallocsa (10 + strlen (source_file) + 1); 254 255 memcpy (option, "-resource:", 10); 256 strcpy (option + 10, source_file); 257 *argp++ = option; 258 } 259 else 260 *argp++ = (char *) source_file; 261 } 262 *argp = NULL; 263 /* Ensure argv length was correctly calculated. */ 264 if (argp - argv != argc) 265 abort (); 266 267 if (verbose) 268 { 269 char *command = shell_quote_argv (argv); 270 printf ("%s\n", command); 271 free (command); 272 } 273 274 child = create_pipe_in ("mcs", "mcs", argv, NULL, false, true, true, fd); 275 276 /* Read the subprocess output, copying it to stderr. Drop the last 277 line if it starts with "Compilation succeeded". */ 278 fp = fdopen (fd[0], "r"); 279 if (fp == NULL) 280 error (EXIT_FAILURE, errno, _("fdopen() failed")); 281 line[0] = NULL; linesize[0] = 0; 282 line[1] = NULL; linesize[1] = 0; 283 l = 0; 284 for (;;) 285 { 286 linelen[l] = getline (&line[l], &linesize[l], fp); 287 if (linelen[l] == (size_t)(-1)) 288 break; 289 l = (l + 1) % 2; 290 if (line[l] != NULL) 291 fwrite (line[l], 1, linelen[l], stderr); 292 } 293 l = (l + 1) % 2; 294 if (line[l] != NULL 295 && !(linelen[l] >= 21 296 && memcmp (line[l], "Compilation succeeded", 21) == 0)) 297 fwrite (line[l], 1, linelen[l], stderr); 298 if (line[0] != NULL) 299 free (line[0]); 300 if (line[1] != NULL) 301 free (line[1]); 302 fclose (fp); 303 304 /* Remove zombie process from process list, and retrieve exit status. */ 305 exitstatus = wait_subprocess (child, "mcs", false, false, true, true); 306 307 for (i = 1 + (output_is_library ? 1 : 0); 308 i < 1 + (output_is_library ? 1 : 0) 309 + 1 + libdirs_count + libraries_count; 310 i++) 311 freesa (argv[i]); 312 for (i = 0; i < sources_count; i++) 313 if (argv[argc - sources_count + i] != sources[i]) 314 freesa (argv[argc - sources_count + i]); 315 freesa (argv); 316 317 return (exitstatus != 0); 318 } 319 else 320 return -1; 321} 322 323static int 324compile_csharp_using_sscli (const char * const *sources, 325 unsigned int sources_count, 326 const char * const *libdirs, 327 unsigned int libdirs_count, 328 const char * const *libraries, 329 unsigned int libraries_count, 330 const char *output_file, bool output_is_library, 331 bool optimize, bool debug, 332 bool verbose) 333{ 334 static bool csc_tested; 335 static bool csc_present; 336 337 if (!csc_tested) 338 { 339 /* Test for presence of csc: 340 "csc -help >/dev/null 2>/dev/null \ 341 && ! { csc -help 2>/dev/null | grep -i chicken > /dev/null; }" */ 342 char *argv[3]; 343 pid_t child; 344 int fd[1]; 345 int exitstatus; 346 347 argv[0] = "csc"; 348 argv[1] = "-help"; 349 argv[2] = NULL; 350 child = create_pipe_in ("csc", "csc", argv, DEV_NULL, true, true, false, 351 fd); 352 csc_present = false; 353 if (child != -1) 354 { 355 /* Read the subprocess output, and test whether it contains the 356 string "chicken". */ 357 char c[7]; 358 size_t count = 0; 359 360 csc_present = true; 361 while (safe_read (fd[0], &c[count], 1) > 0) 362 { 363 if (c[count] >= 'A' && c[count] <= 'Z') 364 c[count] += 'a' - 'A'; 365 count++; 366 if (count == 7) 367 { 368 if (memcmp (c, "chicken", 7) == 0) 369 csc_present = false; 370 c[0] = c[1]; c[1] = c[2]; c[2] = c[3]; 371 c[3] = c[4]; c[4] = c[5]; c[5] = c[6]; 372 count--; 373 } 374 } 375 376 close (fd[0]); 377 378 /* Remove zombie process from process list, and retrieve exit 379 status. */ 380 exitstatus = 381 wait_subprocess (child, "csc", false, true, true, false); 382 if (exitstatus != 0) 383 csc_present = false; 384 } 385 csc_tested = true; 386 } 387 388 if (csc_present) 389 { 390 unsigned int argc; 391 char **argv; 392 char **argp; 393 int exitstatus; 394 unsigned int i; 395 396 argc = 397 1 + 1 + 1 + libdirs_count + libraries_count 398 + (optimize ? 1 : 0) + (debug ? 1 : 0) + sources_count; 399 argv = (char **) xallocsa ((argc + 1) * sizeof (char *)); 400 401 argp = argv; 402 *argp++ = "csc"; 403 *argp++ = (output_is_library ? "-target:library" : "-target:exe"); 404 { 405 char *option = (char *) xallocsa (5 + strlen (output_file) + 1); 406 memcpy (option, "-out:", 5); 407 strcpy (option + 5, output_file); 408 *argp++ = option; 409 } 410 for (i = 0; i < libdirs_count; i++) 411 { 412 char *option = (char *) xallocsa (5 + strlen (libdirs[i]) + 1); 413 memcpy (option, "-lib:", 5); 414 strcpy (option + 5, libdirs[i]); 415 *argp++ = option; 416 } 417 for (i = 0; i < libraries_count; i++) 418 { 419 char *option = (char *) xallocsa (11 + strlen (libraries[i]) + 4 + 1); 420 memcpy (option, "-reference:", 11); 421 memcpy (option + 11, libraries[i], strlen (libraries[i])); 422 strcpy (option + 11 + strlen (libraries[i]), ".dll"); 423 *argp++ = option; 424 } 425 if (optimize) 426 *argp++ = "-optimize+"; 427 if (debug) 428 *argp++ = "-debug+"; 429 for (i = 0; i < sources_count; i++) 430 { 431 const char *source_file = sources[i]; 432 if (strlen (source_file) >= 10 433 && memcmp (source_file + strlen (source_file) - 10, ".resources", 434 10) == 0) 435 { 436 char *option = (char *) xallocsa (10 + strlen (source_file) + 1); 437 438 memcpy (option, "-resource:", 10); 439 strcpy (option + 10, source_file); 440 *argp++ = option; 441 } 442 else 443 *argp++ = (char *) source_file; 444 } 445 *argp = NULL; 446 /* Ensure argv length was correctly calculated. */ 447 if (argp - argv != argc) 448 abort (); 449 450 if (verbose) 451 { 452 char *command = shell_quote_argv (argv); 453 printf ("%s\n", command); 454 free (command); 455 } 456 457 exitstatus = execute ("csc", "csc", argv, false, false, false, false, 458 true, true); 459 460 for (i = 2; i < 3 + libdirs_count + libraries_count; i++) 461 freesa (argv[i]); 462 for (i = 0; i < sources_count; i++) 463 if (argv[argc - sources_count + i] != sources[i]) 464 freesa (argv[argc - sources_count + i]); 465 freesa (argv); 466 467 return (exitstatus != 0); 468 } 469 else 470 return -1; 471} 472 473bool 474compile_csharp_class (const char * const *sources, 475 unsigned int sources_count, 476 const char * const *libdirs, 477 unsigned int libdirs_count, 478 const char * const *libraries, 479 unsigned int libraries_count, 480 const char *output_file, 481 bool optimize, bool debug, 482 bool verbose) 483{ 484 bool output_is_library = 485 (strlen (output_file) >= 4 486 && memcmp (output_file + strlen (output_file) - 4, ".dll", 4) == 0); 487 int result; 488 489 /* First try the C# implementation specified through --enable-csharp. */ 490#if CSHARP_CHOICE_PNET 491 result = compile_csharp_using_pnet (sources, sources_count, 492 libdirs, libdirs_count, 493 libraries, libraries_count, 494 output_file, output_is_library, 495 optimize, debug, verbose); 496 if (result >= 0) 497 return (bool) result; 498#endif 499 500#if CSHARP_CHOICE_MONO 501 result = compile_csharp_using_mono (sources, sources_count, 502 libdirs, libdirs_count, 503 libraries, libraries_count, 504 output_file, output_is_library, 505 optimize, debug, verbose); 506 if (result >= 0) 507 return (bool) result; 508#endif 509 510 /* Then try the remaining C# implementations in our standard order. */ 511#if !CSHARP_CHOICE_PNET 512 result = compile_csharp_using_pnet (sources, sources_count, 513 libdirs, libdirs_count, 514 libraries, libraries_count, 515 output_file, output_is_library, 516 optimize, debug, verbose); 517 if (result >= 0) 518 return (bool) result; 519#endif 520 521#if !CSHARP_CHOICE_MONO 522 result = compile_csharp_using_mono (sources, sources_count, 523 libdirs, libdirs_count, 524 libraries, libraries_count, 525 output_file, output_is_library, 526 optimize, debug, verbose); 527 if (result >= 0) 528 return (bool) result; 529#endif 530 531 result = compile_csharp_using_sscli (sources, sources_count, 532 libdirs, libdirs_count, 533 libraries, libraries_count, 534 output_file, output_is_library, 535 optimize, debug, verbose); 536 if (result >= 0) 537 return (bool) result; 538 539 error (0, 0, _("C# compiler not found, try installing pnet")); 540 return true; 541} 542