/* * %CopyrightBegin% * * Copyright Ericsson AB 1997-2011. 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 /* FIXE ME config_win32.h? */ #define HAVE_STRERROR 1 #define snprintf _snprintf #endif #include #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. */ 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]); if (strlen(emulator) >= MAXPATHLEN) error("Value of environment variable ERLC_EMULATOR is too large"); /* * 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*6+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("+sbtu"); PUSH("-noinput"); PUSH2("-mode", "minimal"); PUSH2("-boot", "start_clean"); PUSH3("-s", "erl_compile", "compile_cmdline"); /* * 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 'I': PUSH2("@i", process_opt(&argc, &argv, 0)); break; case 'M': { char *buf, *key, *val; size_t buf_len; if (argv[1][2] == '\0') { /* -M */ /* Push the following options: * o 'makedep' * o {makedep_output, standard_io} */ buf = strsave("makedep"); PUSH2("@option", buf); key = "makedep_output"; val = "standard_io"; buf_len = 1 + strlen(key) + 1 + strlen(val) + 1 + 1; buf = emalloc(buf_len); snprintf(buf, buf_len, "{%s,%s}", key, val); PUSH2("@option", buf); } else if (argv[1][3] == '\0') { switch(argv[1][2]) { case 'D': /* -MD */ /* Push the following options: * o 'makedep' */ buf = strsave("makedep"); PUSH2("@option", buf); break; case 'F': /* -MF */ /* Push the following options: * o 'makedep' * o {makedep_output, } */ buf = strsave("makedep"); PUSH2("@option", buf); key = "makedep_output"; val = process_opt(&argc, &argv, 1); buf_len = 1 + strlen(key) + 2 + strlen(val) + 2 + 1; buf = emalloc(buf_len); snprintf(buf, buf_len, "{%s,\"%s\"}", key, val); PUSH2("@option", buf); break; case 'T': /* -MT */ /* Push the following options: * o {makedep_target, } */ key = "makedep_target"; val = process_opt(&argc, &argv, 1); buf_len = 1 + strlen(key) + 2 + strlen(val) + 2 + 1; buf = emalloc(buf_len); snprintf(buf, buf_len, "{%s,\"%s\"}", key, val); PUSH2("@option", buf); break; case 'Q': /* -MQ */ /* Push the following options: * o {makedep_target, } * o makedep_quote_target */ key = "makedep_target"; val = process_opt(&argc, &argv, 1); buf_len = 1 + strlen(key) + 2 + strlen(val) + 2 + 1; buf = emalloc(buf_len); snprintf(buf, buf_len, "{%s,\"%s\"}", key, val); PUSH2("@option", buf); buf = strsave("makedep_quote_target"); PUSH2("@option", buf); break; case 'G': /* -MG */ /* Push the following options: * o makedep_add_missing */ buf = strsave("makedep_add_missing"); PUSH2("@option", buf); break; case 'P': /* -MP */ /* Push the following options: * o makedep_phony */ buf = strsave("makedep_add_missing"); PUSH2("@option", buf); break; default: goto error; } } } 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 (strcmp(argv[1]+2, "error") == 0) { PUSH2("@option", "warnings_as_errors"); } 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. */ 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[MAXPATHLEN]; 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"}, {"-help", "shows this help text"}, {"-I path", "where to search for include files"}, {"-M", "generate a rule for make(1) describing the dependencies"}, {"-MF file", "write the dependencies to 'file'"}, {"-MT target", "change the target of the rule emitted by dependency " "generation"}, {"-MQ target", "same as -MT but quote characters special to make(1)"}, {"-MG", "consider missing headers as generated files and add them to " "the dependencies"}, {"-MP", "add a phony target for each dependency"}, {"-MD", "same as -M -MT file (with default 'file')"}, {"-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"}, {"-Werror", "make all warnings into errors"}, {"-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); erts_vsnprintf(sbuf, sizeof(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; if (strlen(progname) >= sizeof(sbuf)) return ERL_NAME; 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__ */