diff options
Diffstat (limited to 'erts/etc/common/erlc.c')
-rw-r--r-- | erts/etc/common/erlc.c | 701 |
1 files changed, 701 insertions, 0 deletions
diff --git a/erts/etc/common/erlc.c b/erts/etc/common/erlc.c new file mode 100644 index 0000000000..c958fed741 --- /dev/null +++ b/erts/etc/common/erlc.c @@ -0,0 +1,701 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1997-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +/* + * Purpose: Common compiler front-end. + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include "sys.h" +#ifdef __WIN32__ +#include <winbase.h> +/* FIXE ME config_win32.h? */ +#define HAVE_STRERROR 1 +#endif + +#include <ctype.h> + +#define NO 0 +#define YES 1 + +#define ASIZE(a) (sizeof(a)/sizeof(a[0])) + +static int debug = 0; /* Bit flags for debug printouts. */ + +static char** eargv_base; /* Base of vector. */ +static char** eargv; /* First argument for erl. */ + +static int eargc; /* Number of arguments in eargv. */ + +#ifdef __WIN32__ +# define QUOTE(s) possibly_quote(s) +# define IS_DIRSEP(c) ((c) == '/' || (c) == '\\') +# define ERL_NAME "erl.exe" +#else +# define QUOTE(s) s +# define IS_DIRSEP(c) ((c) == '/') +# define ERL_NAME "erl" +#endif + +#define UNSHIFT(s) eargc++, eargv--; eargv[0] = QUOTE(s) +#define PUSH(s) eargv[eargc++] = QUOTE(s) +#define PUSH2(s, t) PUSH(s); PUSH(t) +#define PUSH3(s, t, u) PUSH2(s, t); PUSH(u) + +static char* output_type = NULL; /* Type of output file. */ +#ifdef __WIN32__ +static int pause_after_execution = 0; +#endif + +/* + * Local functions. + */ + +static char* process_opt(int* pArgc, char*** pArgv, int offset); +static void error(char* format, ...); +static void usage(void); +static char* emalloc(size_t size); +static char* strsave(char* string); +static void push_words(char* src); +static int run_erlang(char* name, char** argv); +static char* get_default_emulator(char* progname); +#ifdef __WIN32__ +static char* possibly_quote(char* arg); +#endif + +/* + * Supply a strerror() function if libc doesn't. + */ +#ifndef HAVE_STRERROR + +extern int sys_nerr; + +#ifndef SYS_ERRLIST_DECLARED +extern const char * const sys_errlist[]; +#endif /* !SYS_ERRLIST_DECLARED */ + +char *strerror(int errnum) +{ + static char *emsg[1024]; + + if (errnum != 0) { + if (errnum > 0 && errnum < sys_nerr) + sprintf((char *) &emsg[0], "(%s)", sys_errlist[errnum]); + else + sprintf((char *) &emsg[0], "errnum = %d ", errnum); + } + else { + emsg[0] = '\0'; + } + return (char *) &emsg[0]; +} +#endif /* !HAVE_STRERROR */ + +static char * +get_env(char *key) +{ +#ifdef __WIN32__ + DWORD size = 32; + char *value = NULL; + while (1) { + DWORD nsz; + if (value) + free(value); + value = emalloc(size); + SetLastError(0); + nsz = GetEnvironmentVariable((LPCTSTR) key, (LPTSTR) value, size); + if (nsz == 0 && GetLastError() == ERROR_ENVVAR_NOT_FOUND) { + free(value); + return NULL; + } + if (nsz <= size) + return value; + size = nsz; + } +#else + return getenv(key); +#endif +} + +static void +free_env_val(char *value) +{ +#ifdef __WIN32__ + if (value) + free(value); +#endif +} + + +int +main(int argc, char** argv) +{ + char cwd[MAXPATHLEN]; /* Current working directory. */ + char** rpc_eargv; /* Pointer to the beginning of arguments + * if calling a running Erlang system + * via erl_rpc(). + */ + int eargv_size; + int eargc_base; /* How many arguments in the base of eargv. */ + char* emulator; + char *env; + + env = get_env("ERLC_EMULATOR"); + emulator = env ? env : get_default_emulator(argv[0]); + + /* + * Allocate the argv vector to be used for arguments to Erlang. + * Arrange for starting to pushing information in the middle of + * the array, to allow easy adding of emulator options (like -pa) + * before '-s erlcompile compile_cmdline...'. + * + * Oh, by the way, we will push the compiler command in the + * base of the eargv vector, and move it up later. + */ + + eargv_size = argc*4+100; + eargv_base = (char **) emalloc(eargv_size*sizeof(char*)); + eargv = eargv_base; + eargc = 0; + push_words(emulator); + eargc_base = eargc; + eargv = eargv + eargv_size/2; + eargc = 0; + + free_env_val(env); + + /* + * Push initial arguments. + */ + + PUSH("-noinput"); + PUSH2("-mode", "minimal"); + PUSH2("-boot", "start_clean"); + PUSH3("-s", "erl_compile", "compile_cmdline"); + rpc_eargv = eargv+eargc; + + /* + * Push standard arguments to Erlang. + * + * The @cwd argument was once needed, but from on R13B02 is optional. + * For maximum compatibility between erlc and erl of different versions, + * still provide the @cwd argument, unless it is too long to be + * represented as an atom. + */ + if (getcwd(cwd, sizeof(cwd)) == NULL) + error("Failed to get current working directory: %s", strerror(errno)); +#ifdef __WIN32__ + (void) GetShortPathName(cwd, cwd, sizeof(cwd)); +#endif + if (strlen(cwd) < 256) { + PUSH2("@cwd", cwd); + } + + /* + * Parse all command line switches. + */ + + while (argc > 1 && (argv[1][0] == '-' || argv[1][0] == '+')) { + + /* + * Options starting with '+' are passed on to Erlang. + */ + + if (argv[1][0] == '+') { + PUSH2("@option", argv[1]+1); + } else { + /* + * Interpret options starting with '-'. + */ + + switch (argv[1][1]) { + case 'b': + output_type = process_opt(&argc, &argv, 0); + PUSH2("@output_type", output_type); + break; + case 'c': /* Allowed for compatibility with 'erl'. */ + if (strcmp(argv[1], "-compile") != 0) + goto error; + break; + case 'd': + debug = 1; + break; + case 'D': + { + char* def = process_opt(&argc, &argv, 0); + char* equals; + + def = strsave(def); /* Do not clobber original. */ + if ((equals = strchr(def, '=')) == NULL) { + PUSH2("@d", def); + } else { + *equals = '\0'; + equals++; + PUSH3("@dv", def, equals); + } + } + break; + case 'h': + if (strcmp(argv[1], "-hybrid") == 0) { + UNSHIFT(argv[1]); + } else { + usage(); + } + break; + case 'I': + PUSH2("@i", process_opt(&argc, &argv, 0)); + break; + case 'o': + PUSH2("@outdir", process_opt(&argc, &argv, 0)); + break; + case 'O': + PUSH("@optimize"); + if (argv[1][2] == '\0') + PUSH("1"); + else + PUSH(argv[1]+2); + break; + case 'p': + { + int c = argv[1][2]; + + if (c != 'a' && c != 'z') { + goto error; +#ifdef __WIN32__ + } else if (strcmp(argv[1], "-pause") == 0) { + pause_after_execution = 1; +#endif + } else { + char option[4]; + + UNSHIFT(process_opt(&argc, &argv, 1)); + option[0] = '-'; + option[1] = 'p'; + option[2] = c; + option[3] = '\0'; + UNSHIFT(strsave(option)); + } + } + break; + case 's': + if (strcmp(argv[1], "-smp") == 0) { + UNSHIFT(argv[1]); + } else { + goto error; + } + break; + case 'v': /* Verbose. */ + PUSH2("@verbose", "true"); + break; + case 'V': + /** XXX Version perhaps, but of what? **/ + break; + case 'W': /* Enable warnings. */ + if (strcmp(argv[1]+2, "all") == 0) { + PUSH2("@warn", "999"); + } else if (isdigit((int)argv[1][2])) { + PUSH2("@warn", argv[1]+2); + } else { + PUSH2("@warn", "1"); + } + break; + case 'E': + case 'S': + case 'P': + { + char* buf; + + /* + * From the given upper-case letter, construct + * a quoted atom. This is a convenience for the + * Erlang compiler, to avoid fighting with the shell's + * quoting. + */ + + buf = emalloc(4); + buf[0] = '\''; + buf[1] = argv[1][1]; + buf[2] = '\''; + buf[3] = '\0'; + + PUSH2("@option", buf); + } + break; + + case '-': + goto no_more_options; + + default: + error: + usage(); + break; + } + } + argc--, argv++; + } + + no_more_options: + + if (argc <= 1) { + /* + * To avoid starting an Erlang system unless absolutely needed + * exit if no files were specified on the command line. + */ + exit(0); + } + + /* + * The rest of the command line must be filenames. Simply push them. + */ + + PUSH("@files"); + while (argc > 1) { + PUSH(argv[1]); + argc--, argv++; + } + + /* + * Move up the commands for invoking the emulator and adjust eargv + * accordingly. + */ + + while (--eargc_base >= 0) { + UNSHIFT(eargv_base[eargc_base]); + } + + /* + * Invoke Erlang with the collected options. + */ + + PUSH(NULL); + return run_erlang(eargv[0], eargv); +} + +static char* +process_opt(int* pArgc, char*** pArgv, int offset) +{ + int argc = *pArgc; + char** argv = *pArgv; + int c = argv[1][1]; + + if (argv[1][2+offset] != '\0') { + /* + * The option was given as -x<value>. + */ + return argv[1]+2+offset; + } + + /* + * Look at the next argument. + */ + + argc--, argv++; + if (argc < 2 || argv[1][0] == '-') + error("No value given to -%c option", c); + *pArgc = argc; + *pArgv = argv; + return argv[1]; +} + +static void +push_words(char* src) +{ + char sbuf[1024]; + char* dst; + + dst = sbuf; + while ((*dst++ = *src++) != '\0') { + if (isspace((int)*src)) { + *dst = '\0'; + PUSH(strsave(sbuf)); + dst = sbuf; + do { + src++; + } while (isspace((int)*src)); + } + } + if (sbuf[0]) + PUSH(strsave(sbuf)); +} +#ifdef __WIN32__ +char *make_commandline(char **argv) +{ + static char *buff = NULL; + static int siz = 0; + int num = 0; + char **arg, *p; + + if (*argv == NULL) { + return ""; + } + for (arg = argv; *arg != NULL; ++arg) { + num += strlen(*arg)+1; + } + if (!siz) { + siz = num; + buff = malloc(siz*sizeof(char)); + } else if (siz < num) { + siz = num; + buff = realloc(buff,siz*sizeof(char)); + } + p = buff; + for (arg = argv; *arg != NULL; ++arg) { + strcpy(p,*arg); + p+=strlen(*arg); + *p++=' '; + } + *(--p) = '\0'; + + if (debug) { + printf("Processed commandline:%s\n",buff); + } + return buff; +} + +int my_spawnvp(char **argv) +{ + STARTUPINFO siStartInfo; + PROCESS_INFORMATION piProcInfo; + DWORD ec; + + memset(&siStartInfo,0,sizeof(STARTUPINFO)); + siStartInfo.cb = sizeof(STARTUPINFO); + siStartInfo.dwFlags = STARTF_USESTDHANDLES; + siStartInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); + siStartInfo.wShowWindow = SW_HIDE; + siStartInfo.dwFlags |= STARTF_USESHOWWINDOW; + + + if (!CreateProcess(NULL, + make_commandline(argv), + NULL, + NULL, + TRUE, + 0, + NULL, + NULL, + &siStartInfo, + &piProcInfo)) { + return -1; + } + CloseHandle(piProcInfo.hThread); + + WaitForSingleObject(piProcInfo.hProcess,INFINITE); + if (!GetExitCodeProcess(piProcInfo.hProcess,&ec)) { + return 0; + } + return (int) ec; +} +#endif /* __WIN32__ */ + + +static int +run_erlang(char* progname, char** argv) +{ +#ifdef __WIN32__ + int status; +#endif + + if (debug) { + int i = 0; + while (argv[i] != NULL) + printf(" %s", argv[i++]); + printf("\n"); + } + +#ifdef __WIN32__ + /* + * Alas, we must wait here for the program to finish. + * Otherwise, the shell from which we was executed will think + * we are finished and print a prompt and read keyboard input. + */ + + status = my_spawnvp(argv)/*_spawnvp(_P_WAIT,progname,argv)*/; + if (status == -1) { + fprintf(stderr, "erlc: Error executing '%s': %d", progname, + GetLastError()); + } + if (pause_after_execution) { + fprintf(stderr, "Press ENTER to continue . . .\n"); + while (getchar() != '\n') + ; + } + return status; +#else + execvp(progname, argv); + error("Error %d executing \'%s\'.", errno, progname); + return 2; +#endif +} + +static void +usage(void) +{ + static struct { + char* name; + char* desc; + } options[] = { + {"-b type", "type of output file (e.g. jam or beam)"}, + {"-d", "turn on debugging of erlc itself"}, + {"-Dname", "define name"}, + {"-Dname=value", "define name to have value"}, + {"-hybrid", "compile using hybrid-heap emulator"}, + {"-help", "shows this help text"}, + {"-I path", "where to search for include files"}, + {"-o name", "name output directory or file"}, + {"-pa path", "add path to the front of Erlang's code path"}, + {"-pz path", "add path to the end of Erlang's code path"}, + {"-smp", "compile using SMP emulator"}, + {"-v", "verbose compiler output"}, + {"-W0", "disable warnings"}, + {"-Wnumber", "set warning level to number"}, + {"-Wall", "enable all warnings"}, + {"-W", "enable warnings (default; same as -W1)"}, + {"-E", "generate listing of expanded code (Erlang compiler)"}, + {"-S", "generate assembly listing (Erlang compiler)"}, + {"-P", "generate listing of preprocessed code (Erlang compiler)"}, + {"+term", "pass the Erlang term unchanged to the compiler"}, + }; + int i; + + fprintf(stderr, "Usage:\terlc [options] file.ext ...\n"); + fprintf(stderr, "Options:\n"); + for (i = 0; i < sizeof(options)/sizeof(options[0]); i++) { + fprintf(stderr, "%-14s %s\n", options[i].name, options[i].desc); + } + exit(1); +} + +static void +error(char* format, ...) +{ + char sbuf[1024]; + va_list ap; + + va_start(ap, format); + vsprintf(sbuf, format, ap); + va_end(ap); + fprintf(stderr, "erlc: %s\n", sbuf); + exit(1); +} + +static char* +emalloc(size_t size) +{ + char *p = malloc(size); + if (p == NULL) + error("Insufficient memory"); + return p; +} + +static char* +strsave(char* string) +{ + char* p = emalloc(strlen(string)+1); + strcpy(p, string); + return p; +} + +static char* +get_default_emulator(char* progname) +{ + char sbuf[MAXPATHLEN]; + char* s; + + strcpy(sbuf, progname); + for (s = sbuf+strlen(sbuf); s >= sbuf; s--) { + if (IS_DIRSEP(*s)) { + strcpy(s+1, ERL_NAME); +#ifdef __WIN32__ + if (_access(sbuf, 0) != -1) { + return strsave(sbuf); + } +#else + if (access(sbuf, 1) != -1) { + return strsave(sbuf); + } +#endif + break; + } + } + return ERL_NAME; +} + +#ifdef __WIN32__ +static char* +possibly_quote(char* arg) +{ + int mustQuote = NO; + int n = 0; + char* s; + char* narg; + + if (arg == NULL) { + return arg; + } + + /* + * Scan the string to find out if it needs quoting and return + * the original argument if not. + */ + + for (s = arg; *s; s++, n++) { + switch(*s) { + case ' ': + mustQuote = YES; + continue; + case '"': + mustQuote = YES; + n++; + continue; + case '\\': + if(s[1] == '"') + n++; + continue; + default: + continue; + } + } + if (!mustQuote) { + return arg; + } + + /* + * Insert the quotes and put a backslash in front of every quote + * inside the string. + */ + + s = narg = emalloc(n+2+1); + for (*s++ = '"'; *arg; arg++, s++) { + if (*arg == '"' || (*arg == '\\' && arg[1] == '"')) { + *s++ = '\\'; + } + *s = *arg; + } + if (s[-1] == '\\') { + *s++ ='\\'; + } + *s++ = '"'; + *s = '\0'; + return narg; +} +#endif /* __WIN32__ */ |