diff options
-rw-r--r-- | Makefile.in | 11 | ||||
-rw-r--r-- | bootstrap/lib/kernel/ebin/erl_compile_server.beam | bin | 0 -> 4928 bytes | |||
-rw-r--r-- | bootstrap/lib/kernel/ebin/kernel.app | 1 | ||||
-rw-r--r-- | bootstrap/lib/kernel/ebin/kernel.beam | bin | 3624 -> 3744 bytes | |||
-rw-r--r-- | bootstrap/lib/stdlib/ebin/erl_compile.beam | bin | 6624 -> 6756 bytes | |||
-rw-r--r-- | erts/doc/src/erlc.xml | 63 | ||||
-rw-r--r-- | erts/emulator/Makefile.in | 4 | ||||
-rw-r--r-- | erts/etc/common/Makefile.in | 12 | ||||
-rw-r--r-- | erts/etc/common/erlc.c | 544 | ||||
-rw-r--r-- | erts/test/erlc_SUITE.erl | 23 | ||||
-rw-r--r-- | lib/erl_interface/doc/src/Makefile | 2 | ||||
-rw-r--r-- | lib/erl_interface/src/Makefile | 2 | ||||
-rw-r--r-- | lib/kernel/src/Makefile | 1 | ||||
-rw-r--r-- | lib/kernel/src/erl_compile_server.erl | 253 | ||||
-rw-r--r-- | lib/kernel/src/kernel.app.src | 1 | ||||
-rw-r--r-- | lib/kernel/src/kernel.erl | 17 | ||||
-rw-r--r-- | lib/stdlib/doc/src/digraph.xml | 21 | ||||
-rw-r--r-- | lib/stdlib/src/erl_compile.erl | 187 | ||||
-rwxr-xr-x | scripts/build-otp | 1 |
19 files changed, 1004 insertions, 139 deletions
diff --git a/Makefile.in b/Makefile.in index 55decd8794..133ea0f4d6 100644 --- a/Makefile.in +++ b/Makefile.in @@ -338,7 +338,8 @@ endif else # Cross compiling -all: cross_check_erl depend emulator libs start_scripts check_dev_rt_dep +all: cross_check_erl depend build_erl_interface \ + emulator libs start_scripts check_dev_rt_dep endif @@ -376,11 +377,17 @@ endif # With all bootstraps we mean all bootstrapping that is done when # the system is delivered in open source, the primary # bootstrap is not included, it requires a pre built emulator... -all_bootstraps: emulator \ +all_bootstraps: build_erl_interface emulator \ bootstrap_setup \ secondary_bootstrap_build secondary_bootstrap_copy \ tertiary_bootstrap_build tertiary_bootstrap_copy +.PHONY: build_erl_interface + +build_erl_interface: + $(make_verbose)cd lib/erl_interface && \ + ERL_TOP=$(ERL_TOP) PATH=$(BOOT_PREFIX)"$${PATH}" \ + $(MAKE) $(TYPE) # # Use these targets when you want to use the erl and erlc # binaries in your PATH instead of those created from the diff --git a/bootstrap/lib/kernel/ebin/erl_compile_server.beam b/bootstrap/lib/kernel/ebin/erl_compile_server.beam Binary files differnew file mode 100644 index 0000000000..43c77c4d7e --- /dev/null +++ b/bootstrap/lib/kernel/ebin/erl_compile_server.beam diff --git a/bootstrap/lib/kernel/ebin/kernel.app b/bootstrap/lib/kernel/ebin/kernel.app index cb13db1a89..7483d1bae3 100644 --- a/bootstrap/lib/kernel/ebin/kernel.app +++ b/bootstrap/lib/kernel/ebin/kernel.app @@ -32,6 +32,7 @@ code_server, dist_util, erl_boot_server, + erl_compile_server, erl_distribution, erl_reply, erl_signal_handler, diff --git a/bootstrap/lib/kernel/ebin/kernel.beam b/bootstrap/lib/kernel/ebin/kernel.beam Binary files differindex 7e653a8d5b..78c073ed5c 100644 --- a/bootstrap/lib/kernel/ebin/kernel.beam +++ b/bootstrap/lib/kernel/ebin/kernel.beam diff --git a/bootstrap/lib/stdlib/ebin/erl_compile.beam b/bootstrap/lib/stdlib/ebin/erl_compile.beam Binary files differindex 2f5790b4a7..ca55b187e8 100644 --- a/bootstrap/lib/stdlib/ebin/erl_compile.beam +++ b/bootstrap/lib/stdlib/ebin/erl_compile.beam diff --git a/erts/doc/src/erlc.xml b/erts/doc/src/erlc.xml index 62957d6a50..9d221a69ee 100644 --- a/erts/doc/src/erlc.xml +++ b/erts/doc/src/erlc.xml @@ -138,6 +138,16 @@ for compiling native code, which must be compiled with the same runtime system that it is to be run on.</p> </item> + <tag><c>-no-server</c></tag> + <item> + <p>Do not use the + <seealso marker="#compile_server">compile server</seealso>.</p> + </item> + <tag><c>-server</c></tag> + <item> + <p>Use the + <seealso marker="#compile_server">compile server</seealso>.</p> + </item> <tag><c>-M</c></tag> <item> <p>Produces a Makefile rule to track header dependencies. The @@ -298,6 +308,52 @@ erlc +export_all file.erl</pre> </section> <section> + <marker id="compile_server"></marker> + <title>Compile Server</title> + <p>The compile server can be used to potentially speed up the + build of multi-file projects by avoiding to start an Erlang system + for each file to compile. Whether it will speed up the build + depends on the nature of the project and the build machine.</p> + + <p>By default, the compile server is not used. It can be + enabled by giving <c>erlc</c> the option <c>-server</c> or by + setting the environment variable <c>ERLC_USE_SERVER</c> to + <c>yes</c> or <c>true</c>.</p> + + <p>When the compile server is enabled, <c>erlc</c> will + automatically use the server if it is started and start the server + if has not already started. The server will terminate itself when + it has been idle for some number of seconds.</p> + + <p><c>erlc</c> and the compile server communicate using the + Erlang distribution. The compile server is started as a hidden + node, with a name that includes the current user. Thus, each user + on a computer has their own compile server.</p> + + <p>Using the compile server does not always speed up the build, as + the compile server sometimes must be restarted to ensure correctness. + Here are some examples of situtations that force a restart:</p> + + <list type="bulleted"> + <item><c>erlc</c> wants to use a different version of Erlang + than the compile server is using.</item> + <item><c>erlc</c> wants to use different options for <c>erl</c> + than the compile server was started with. (A change to code path + using the option <c>-pa</c> could cause different parse + transforms to be loaded. To be safe, the compile server will be + restarted when any <c>erl</c> option is changed.)</item> + <item>If the current working directory for <c>erlc</c> is + different from the working directory active when the compile + server was started, <strong>and</strong> if the compile server + has active jobs, it will be restarted as soon as those jobs have + finished. (Build systems that build files randomly across multiple + directories in parallel will probably not benefit from the + compile server.)</item> + </list> + </section> + + <section> + <marker id="environment_variables"></marker> <title>Environment Variables</title> <taglist> <tag><c>ERLC_EMULATOR</c></tag> @@ -305,6 +361,13 @@ erlc +export_all file.erl</pre> in the same directory as the <c>erlc</c> program itself, or, if it does not exist, <c>erl</c> in any of the directories specified in environment variable <c>PATH</c>.</item> + <tag><c>ERLC_USE_SERVER</c></tag> + <item>Allowed values are <c>yes</c> or <c>true</c> to use the + <seealso marker="#compile_server">compile + server</seealso>, and <c>no</c> or <c>false</c> to not use the + compile server. If other values are given, <c>erlc</c> will + print a warning message and continue. + </item> </taglist> </section> diff --git a/erts/emulator/Makefile.in b/erts/emulator/Makefile.in index a8b1728b90..b2b8acd1b0 100644 --- a/erts/emulator/Makefile.in +++ b/erts/emulator/Makefile.in @@ -741,8 +741,8 @@ $(OBJDIR)/PROFILE: $(BINDIR)/$(PROFILE_EXECUTABLE) $(V_at)echo " PROFILE ${PROFILE_EXECUTABLE}" $(V_at)rm -f $(OBJDIR)/erl*.profraw $(V_at)set -e; LLVM_PROFILE_FILE="$(OBJDIR)/erlc-%m.profraw" \ - ERL_FLAGS="-emu_type prof${TYPEMARKER} +S 1" $(ERLC) -DPGO \ - -o $(OBJDIR) test/estone_SUITE.erl > $(OBJDIR)/PROFILE_LOG + ERL_FLAGS="-emu_type prof${TYPEMARKER} +S 1" $(ERLC) -no-server \ + -DPGO -o $(OBJDIR) test/estone_SUITE.erl > $(OBJDIR)/PROFILE_LOG $(V_at)set -e; LLVM_PROFILE_FILE="$(OBJDIR)/erl-%m.profraw" \ ERL_FLAGS="-emu_type prof${TYPEMARKER} +S 1" $(ERL) -pa $(OBJDIR) \ -noshell -s estone_SUITE pgo -s init stop >> $(OBJDIR)/PROFILE_LOG diff --git a/erts/etc/common/Makefile.in b/erts/etc/common/Makefile.in index 1f35cef669..3a03374fbf 100644 --- a/erts/etc/common/Makefile.in +++ b/erts/etc/common/Makefile.in @@ -54,6 +54,8 @@ ERTS_INCL = -I$(ERL_TOP)/erts/include \ -I$(ERL_TOP)/erts/include/internal \ -I$(ERL_TOP)/erts/include/internal/$(TARGET) +EI_INCL = -I$(ERL_TOP)/lib/erl_interface/include + CC = @CC@ WFLAGS = @WFLAGS@ CFLAGS = @CFLAGS@ @DEFS@ $(TYPE_FLAGS) @WFLAGS@ -I$(SYSOSDIR) -I$(EMUDIR) -I. \ @@ -87,6 +89,10 @@ DRVDIR = $(ERL_TOP)/erts/emulator/drivers/@ERLANG_OSTYPE@ UXETC = ../unix WINETC = ../win32 +# Threads flags and libs +THR_DEFS=@THR_DEFS@ +THR_LIBS=@THR_LIBS@ + ifeq ($(TARGET), win32) ETC = $(WINETC) else @@ -108,6 +114,8 @@ endif ERTS_LIB = $(ERL_TOP)/erts/lib_src/obj/$(TARGET)/$(TYPE)/MADE +EI_LIB = -L$(ERL_TOP)/lib/erl_interface/obj/$(TARGET) -lei $(THR_LIBS) + # ---------------------------------------------------- # Release directory specification # ---------------------------------------------------- @@ -426,10 +434,10 @@ $(OBJDIR)/$(ERLEXEC).o: $(ERLEXECDIR)/$(ERLEXEC).c $(RC_GENERATED) endif $(BINDIR)/erlc@EXEEXT@: $(OBJDIR)/erlc.o $(ERTS_LIB) - $(ld_verbose)$(PURIFY) $(LD) $(LDFLAGS) -o $@ $(OBJDIR)/erlc.o -L$(OBJDIR) $(LIBS) $(ERTS_INTERNAL_LIBS) + $(ld_verbose)$(PURIFY) $(LD) $(LDFLAGS) -o $@ $(OBJDIR)/erlc.o -L$(OBJDIR) $(LIBS) $(ERTS_INTERNAL_LIBS) $(EI_LIB) $(OBJDIR)/erlc.o: erlc.c $(RC_GENERATED) - $(V_CC) $(CFLAGS) -o $@ -c erlc.c + $(V_CC) $(CFLAGS) $(THR_DEFS) $(EI_INCL) $(MT_FLAG) -o $@ -c erlc.c $(BINDIR)/dialyzer@EXEEXT@: $(OBJDIR)/dialyzer.o $(ERTS_LIB) $(ld_verbose)$(PURIFY) $(LD) $(LDFLAGS) -o $@ $(OBJDIR)/dialyzer.o -L$(OBJDIR) $(LIBS) $(ERTS_INTERNAL_LIBS) diff --git a/erts/etc/common/erlc.c b/erts/etc/common/erlc.c index aa99c69100..a9ee023727 100644 --- a/erts/etc/common/erlc.c +++ b/erts/etc/common/erlc.c @@ -20,14 +20,32 @@ /* * Purpose: Common compiler front-end. */ + +#ifdef __WIN32__ +# include <winsock2.h> +#else +# include <stdio.h> +# include <limits.h> +# include <stdlib.h> +# include <signal.h> +# include <sys/stat.h> +#endif + #include "etc_common.h" +#include "ei.h" #define NO 0 #define YES 1 #define ASIZE(a) (sizeof(a)/sizeof(a[0])) -static int debug = 0; /* Bit flags for debug printouts. */ +#ifndef PATH_MAX +# define PATH_MAX 4096 +#endif + +static int debug = 0; /* Debug level. */ +static int use_server = 0; /* Use compile server. */ +static char* source_file = "<no source>"; /* Source file (last argument). */ static char** eargv_base; /* Base of vector. */ static char** eargv; /* First argument for erl. */ @@ -57,6 +75,7 @@ static int pause_after_execution = 0; * Local functions. */ +static void get_env_compile_server(void); static char* process_opt(int* pArgc, char*** pArgv, int offset); static void error(char* format, ...); static void* emalloc(size_t size); @@ -66,7 +85,17 @@ static void efree(void *p); static char* strsave(char* string); static void push_words(char* src); static int run_erlang(char* name, char** argv); +static void call_compile_server(char** argv); +static void encode_env(ei_x_buff* buf); +#ifndef __WIN32__ +static char* find_executable(char* progname); +static char* safe_realpath(char* file); +#endif +static char* get_encoding(void); +static char* decode_binary(const char* buf, int* dec_index, int* dec_size); +static void start_compile_server(char* node_name, char** argv); static char* get_default_emulator(char* progname); +static char* possibly_unquote(char* arg); #ifdef __WIN32__ static char* possibly_quote(char* arg); static void* erealloc(void *p, size_t size); @@ -194,18 +223,43 @@ int main(int argc, char** argv) argv[argc] = NULL; #endif + get_env_compile_server(); + + ei_init(); + 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"); +#ifndef __WIN32__ + emulator = find_executable(emulator); +#endif + /* * Add scriptname to env */ + set_env("ESCRIPT_NAME", argv[0]); /* + * Save a piece of configuration in an environment variable. The + * point is that the compile server needs to know that the same + * Erlang/OTP system would be started. On Unix, we save the full + * path to the Erlang emulator. On Windows, we save the value of + * the environment variable PATH. If the compile server finds that + * another Erlang/OTP system would be started, it will terminate + * itself. + */ + +#ifdef __WIN32__ + set_env("ERLC_CONFIGURATION", get_env("PATH")); +#else + set_env("ERLC_CONFIGURATION", emulator); +#endif + + /* * 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) @@ -252,6 +306,7 @@ int main(int argc, char** argv) * Options starting with '+' are passed on to Erlang. */ + source_file = "<no source>"; switch (argv[1][0]) { case '+': PUSH(argv[1]); @@ -260,11 +315,18 @@ int main(int argc, char** argv) switch (argv[1][1]) { case 'd': if (argv[1][2] == '\0') { - debug = 1; + debug++; } else { PUSH(argv[1]); } break; + case 'n': + if (strcmp(argv[1], "-no-server") == 0) { + use_server = 0; + } else { + PUSH(argv[1]); + } + break; case 'p': { int c = argv[1][2]; @@ -290,6 +352,8 @@ int main(int argc, char** argv) case 's': if (strcmp(argv[1], "-smp") == 0) { UNSHIFT(argv[1]); + } else if (strcmp(argv[1], "-server") == 0) { + use_server = 1; } else { PUSH(argv[1]); } @@ -300,6 +364,7 @@ int main(int argc, char** argv) } break; default: + source_file = argv[1]; PUSH(argv[1]); break; } @@ -320,6 +385,9 @@ int main(int argc, char** argv) */ PUSH(NULL); + if (use_server) { + call_compile_server(eargv); + } return run_erlang(eargv[0], eargv); } @@ -350,6 +418,45 @@ process_opt(int* pArgc, char*** pArgv, int offset) } static void +get_env_compile_server(void) +{ + char* us = get_env("ERLC_USE_SERVER"); + + if (us == NULL) { + return; /* Keep default */ + } + + switch (us[0]) { + case 'f': + if (strcmp(us+1, "alse") == 0) { + use_server = 0; + return; + } + break; + case 'n': + if (strcmp(us+1, "o") == 0) { + use_server = 0; + return; + } + break; + case 't': + if (strcmp(us+1, "rue") == 0) { + use_server = 1; + return; + } + break; + case 'y': + if (strcmp(us+1, "es") == 0) { + use_server = 1; + return; + } + break; + } + fprintf(stderr, "erlc: Warning: Ignoring unrecognized value '%s' " + "for environment value ERLC_USE_SERVER\n", us); +} + +static void push_words(char* src) { char sbuf[MAXPATHLEN]; @@ -369,11 +476,12 @@ push_words(char* src) if (sbuf[0]) PUSH(strsave(sbuf)); } + #ifdef __WIN32__ wchar_t *make_commandline(char **argv) { - static wchar_t *buff = NULL; - static int siz = 0; + wchar_t *buff = NULL; + int siz = 0; int num = 0, len; char **arg; wchar_t *p; @@ -400,17 +508,17 @@ wchar_t *make_commandline(char **argv) } *(--p) = L'\0'; - if (debug) { - printf("Processed command line:%S\n",buff); + if (debug > 1) { + fprintf(stderr, "Processed command line: %S\n", buff); } return buff; } -int my_spawnvp(char **argv) +int my_spawnvp(int wait, char **argv) { STARTUPINFOW siStartInfo; PROCESS_INFORMATION piProcInfo; - DWORD ec; + DWORD ec = 0; memset(&siStartInfo,0,sizeof(STARTUPINFOW)); siStartInfo.cb = sizeof(STARTUPINFOW); @@ -436,12 +544,14 @@ int my_spawnvp(char **argv) } CloseHandle(piProcInfo.hThread); - WaitForSingleObject(piProcInfo.hProcess,INFINITE); - if (!GetExitCodeProcess(piProcInfo.hProcess,&ec)) { - return 0; + if (wait) { + WaitForSingleObject(piProcInfo.hProcess,INFINITE); + if (!GetExitCodeProcess(piProcInfo.hProcess,&ec)) { + return 0; + } } return (int) ec; -} +} #endif /* __WIN32__ */ @@ -452,11 +562,18 @@ run_erlang(char* progname, char** argv) int status; #endif - if (debug) { + if (debug > 0) { + fprintf(stderr, "spawning erl for %s", source_file); + } + if (debug > 1) { int i = 0; - while (argv[i] != NULL) - printf(" %s", argv[i++]); - printf("\n"); + fprintf(stderr, ":\n "); + while (argv[i] != NULL) { + fprintf(stderr, "%s ", argv[i++]); + } + } + if (debug) { + putc('\n', stderr); } #ifdef __WIN32__ @@ -466,7 +583,7 @@ run_erlang(char* progname, char** argv) * we are finished and print a prompt and read keyboard input. */ - status = my_spawnvp(argv)/*_spawnvp(_P_WAIT,progname,argv)*/; + status = my_spawnvp(1, argv); if (status == -1) { fprintf(stderr, "erlc: Error executing '%s': %d", progname, GetLastError()); @@ -478,13 +595,367 @@ run_erlang(char* progname, char** argv) } return status; #else - execvp(progname, argv); + execv(progname, argv); error("Error %d executing \'%s\'.", errno, progname); return 2; #endif } static void +call_compile_server(char** argv) +{ + ei_cnode ec; + char* user; + char node_name[MAXNODELEN+1]; + char remote[MAXNODELEN+1]; + short creation = 1; + int fd; + char cwd[MAXPATHLEN+1]; + ei_x_buff args; + ei_x_buff reply; + int reply_size; + int dec_size, dec_index; + char atom[MAXATOMLEN]; + int argc; + +#ifdef __WIN32__ + if (_getcwd(cwd, sizeof(cwd)) == 0) { + fprintf(stderr, "erlc: failed to get current working directory\n"); + exit(2); + } +#else + if (getcwd(cwd, sizeof(cwd)) == 0) { + fprintf(stderr, "erlc: failed to get current working directory\n"); + exit(2); + } +#endif + +#ifndef __WIN32__ + { + struct sigaction act; + + /* + * If the node is terminating when ei_rpc() is executed, the process + * may receive a SIGPIPE signal. Make sure it does not kill this process. + */ + act.sa_handler = SIG_IGN; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + sigaction(SIGPIPE, &act, NULL); + } +#endif + + /* Get user name */ + user = get_env("USERNAME"); /* Windows */ + if (!user) { + user = get_env("LOGNAME"); /* Unix */ + } + if (!user) { + user = get_env("USER"); /* Unix */ + } + if (!user) { + user = "nouser"; + } + + /* Create my own node name. */ +#ifdef __WIN32__ + sprintf(node_name, "erlc_client_%s_%lu", user, (unsigned long) GetCurrentProcessId()); +#else + sprintf(node_name, "erlc_client_%s_%d", user, getpid()); +#endif + + if (ei_connect_init(&ec, node_name, NULL, creation) < 0) { + /* + * There is probably no .erlang.cookie file. + */ + if (debug > 1) { + fprintf(stderr, "\ncan't create C node %s: %s\n", + node_name, strerror(erl_errno)); + } + sprintf(remote, "erl_compile_server_%s@host", user); + goto start_compile_server; + } + + /* Create node name for compile server. */ + + sprintf(remote, "erl_compile_server_%s@%s", user, ei_thishostname(&ec)); + + if ((fd = ei_connect(&ec, remote)) < 0) { + if (debug > 1) { + fprintf(stderr, "failed to connect to compile server %s: %s\n", + remote, strerror(erl_errno)); + } + goto start_compile_server; + } + + /* + * Encode the request to the compile server. + */ + + ei_x_new_with_version(&args); + ei_x_encode_list_header(&args, 1); + ei_x_encode_map_header(&args, 4); + ei_x_encode_atom(&args, "encoding"); + ei_x_encode_atom(&args, get_encoding()); + ei_x_encode_atom(&args, "cwd"); + ei_x_encode_string(&args, cwd); + ei_x_encode_atom(&args, "env"); + encode_env(&args); + ei_x_encode_atom(&args, "command_line"); + argc = 0; + while (argv[argc]) { + ei_x_encode_list_header(&args, 1); + ei_x_encode_string(&args, possibly_unquote(argv[argc])); + argc++; + } + ei_x_encode_empty_list(&args); /* End of command_line */ + ei_x_encode_empty_list(&args); /* End of argument list for apply */ + + /* + * Do a RPC to the compile server. + */ + + ei_x_new_with_version(&reply); + reply_size = ei_rpc(&ec, fd, "erl_compile_server", "compile", + args.buff+1, args.index-1, &reply); + if (reply_size < 0) { + if (debug > 1) { + fprintf(stderr, "failed to rpc to node %s: %s\n", + remote, strerror(erl_errno)); + } + goto start_compile_server; + } + + /* + * Decode the answer. + */ + + dec_index = 0; + if (ei_decode_atom(reply.buff, &dec_index, atom) == 0 && + strcmp(atom, "wrong_config") == 0) { + if (debug > 1) { + fprintf(stderr, "wrong configuration\n"); + } + goto start_compile_server; + } else if (ei_decode_tuple_header(reply.buff, &dec_index, &dec_size) == 0) { + atom[0] = '\0'; + if (dec_size >= 2) { + ei_decode_atom(reply.buff, &dec_index, atom); + } + if (dec_size == 2) { + if (strcmp(atom, "ok") == 0) { + char* output = decode_binary(reply.buff, &dec_index, &dec_size); + if (debug) { + fprintf(stderr, "called server for %s => ok\n", source_file); + } + if (output) { + fwrite(output, dec_size, 1, stdout); + exit(0); + } + } + } else if (dec_size == 3 && strcmp(atom, "error") == 0) { + int std_size, err_size; + char* std; + char* err; + + if (debug) { + fprintf(stderr, "called server for %s => error\n", source_file); + } + std = decode_binary(reply.buff, &dec_index, &std_size); + err = decode_binary(reply.buff, &dec_index, &err_size); + if (std && err) { + fwrite(err, err_size, 1, stderr); + fwrite(std, std_size, 1, stdout); + exit(1); + } + } + } + + /* + * Unrecognized term, probably because the node was shutting down. + */ + + if (debug > 1) { + fprintf(stderr, "unrecognized term returned by compilation server:\n"); + dec_index = 0; + ei_print_term(stderr, reply.buff, &dec_index); + putc('\n', stderr); + } + + start_compile_server: + *strchr(remote, '@') = '\0'; + start_compile_server(remote, argv); +} + +static void +encode_env(ei_x_buff* buf) +{ + char* env_names[] = {"ERL_AFLAGS", + "ERL_FLAGS", + "ERL_ZFLAGS", + "ERL_COMPILER_OPTIONS", + "ERL_LIBS", + "ERLC_CONFIGURATION", + 0}; + char** p = env_names; + while (p[0]) { + char* val; + + if ((val = get_env(p[0])) != 0) { + ei_x_encode_list_header(buf, 1); + ei_x_encode_tuple_header(buf, 2); + ei_x_encode_string(buf, p[0]); + ei_x_encode_string(buf, val); + } + p++; + } + ei_x_encode_empty_list(buf); +} + +#ifndef __WIN32__ +static char* +find_executable(char* progname) +{ + char* path; + char* start_path; + char* real_name; + char buf[PATH_MAX]; + size_t len_component; + size_t len_prog; + + if (strchr(progname, '/')) { + return progname; + } + + len_prog = strlen(progname); + + if (!(path = getenv("PATH"))) { + path = "/bin:/usr/bin"; + } + + do { + for (start_path = path; *path != '\0' && *path != ':'; path++) { + ; + } + if (start_path == path) { + start_path = "."; + len_component = 1; + } else { + len_component = path - start_path; + } + memcpy(buf, start_path, len_component); + buf[len_component] = '/'; + memcpy(buf + len_component + 1, progname, len_prog); + buf[len_component + len_prog + 1] = '\0'; + if ((real_name = safe_realpath(buf)) != 0) { + struct stat s; + if (stat(real_name, &s) == 0 && s.st_mode & S_IFREG) { + return real_name; + } + } + } while (*path++ == ':'); + return progname; +} + +static char* +safe_realpath(char* file) +{ + /* + * Always allocate a buffer for the result of realpath(). + * realpath() on old versions of MacOS X will crash if the buffer + * argument is NULL, and realpath() will fail on old versions of + * Solaris. + */ + char* real_name = emalloc(PATH_MAX + 1); + return realpath(file, real_name); +} +#endif + +static char* +get_encoding(void) +{ +#ifdef __WIN32__ + return "latin1"; +#else + char* p; + p = get_env("LC_ALL"); + if (!p) { + p = get_env("LC_CTYPE"); + } + if (!p) { + p = get_env("LANG"); + } + if (!p) { + return "latin1"; + } else { + return strstr(p, "UTF-8") ? "utf8" : "latin1"; + } +#endif +} + +static char* +decode_binary(const char* buf, int* dec_index, int* dec_size) +{ + int dec_type; + char* bin; + + ei_get_type(buf, dec_index, &dec_type, dec_size); + bin = emalloc(*dec_size); + if (ei_decode_binary(buf, dec_index, bin, NULL) < 0) { + return NULL; + } + return bin; +} + +static void +start_compile_server(char* node_name, char** argv) +{ + char* eargv[100]; + int eargc = 0; + char* progname = argv[0]; + + while (strcmp(argv[0], "-mode") != 0) { + eargv[eargc++] = *argv++; + } + PUSH2("-boot", "no_dot_erlang"); + PUSH2("-sname", node_name); + PUSH("-hidden"); + PUSH("-detached"); + PUSH3("-kernel", "start_compile_server", "true"); + + /* + * If this is an older Erlang system (before 22.1) that does not + * support the compile server, terminate immediately. + */ + PUSH2("-eval", "is_pid(whereis(erl_compile_server)) orelse halt(1)"); + + PUSH(NULL); + + if (debug == 1) { + fprintf(stderr, "starting compile server %s\n", node_name); + } else if (debug > 1) { + int i = 0; + fprintf(stderr, "starting compile server %s:\n", node_name); + while (eargv[i] != NULL) { + fprintf(stderr, "%s ", eargv[i++]); + } + putc('\n', stderr); + } + +#ifdef __WIN32__ + if (my_spawnvp(0, eargv) == -1) { + fprintf(stderr, "erlc: Error executing '%s': %d", progname, + GetLastError()); + } +#else + if (fork() == 0) { + execv(eargv[0], eargv); + error("Error %d executing \'%s\'.", errno, progname); + } +#endif +} + +static void error(char* format, ...) { char sbuf[1024]; @@ -565,6 +1036,42 @@ get_default_emulator(char* progname) return ERL_NAME; } + +static char* +possibly_unquote(char* arg) +{ +#ifndef __WIN32__ + /* Nothing to do if not Windows. */ + return arg; +#else + char* unquoted; + char* dstp; + + if (arg[0] != '"') { + /* Not quoted. Nothing to do. */ + return arg; + } + + /* + * Remove the quotes and remove backslashes before quotes. + */ + + unquoted = emalloc(strlen(arg) + 1); + arg++; + dstp = unquoted; + while (*arg) { + if (arg[0] == '\\' && arg[1] == '"') { + *dstp++ = '"'; + arg += 2; + } else { + *dstp++ = *arg++; + } + } + *--dstp = 0; + return unquoted; +#endif +} + #ifdef __WIN32__ static char* possibly_quote(char* arg) @@ -623,4 +1130,5 @@ possibly_quote(char* arg) *s = '\0'; return narg; } + #endif /* __WIN32__ */ diff --git a/erts/test/erlc_SUITE.erl b/erts/test/erlc_SUITE.erl index 0c5b9f8358..c01506b1cd 100644 --- a/erts/test/erlc_SUITE.erl +++ b/erts/test/erlc_SUITE.erl @@ -32,22 +32,34 @@ suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> + [{group,with_server},{group,without_server}]. + +groups() -> + Tests = tests(), + [{with_server,[],Tests}, + {without_server,[],Tests}]. + +tests() -> [compile_erl, compile_yecc, compile_script, compile_mib, good_citizen, deep_cwd, arg_overflow, make_dep_options]. -groups() -> - []. - init_per_suite(Config) -> Config. end_per_suite(_Config) -> ok. -init_per_group(_GroupName, Config) -> +init_per_group(with_server, Config) -> + os:putenv("ERLC_USE_SERVER", "yes"), + Config; +init_per_group(without_server, Config) -> + os:putenv("ERLC_USE_SERVER", "no"), + Config; +init_per_group(_, Config) -> Config. end_per_group(_GroupName, Config) -> + os:unsetenv("ERLC_USE_SERVER"), Config. %% Copy from erlc_SUITE_data/include/erl_test.hrl. @@ -199,8 +211,7 @@ deep_cwd(Config) when is_list(Config) -> deep_cwd_1(PrivDir) -> DeepDir0 = filename:join(PrivDir, lists:duplicate(128, $a)), DeepDir = filename:join(DeepDir0, lists:duplicate(128, $b)), - ok = file:make_dir(DeepDir0), - ok = file:make_dir(DeepDir), + ok = filelib:ensure_dir(filename:join(DeepDir,"any_file")), ok = file:set_cwd(DeepDir), ok = file:write_file("test.erl", "-module(test).\n\n"), io:format("~s\n", [os:cmd("erlc test.erl")]), diff --git a/lib/erl_interface/doc/src/Makefile b/lib/erl_interface/doc/src/Makefile index 507a84a453..03044a0ddd 100644 --- a/lib/erl_interface/doc/src/Makefile +++ b/lib/erl_interface/doc/src/Makefile @@ -96,7 +96,7 @@ man: $(MAN1_FILES) $(MAN3_FILES) gifs: $(GIF_FILES:%=$(HTMLDIR)/%) -debug opt: +debug opt lcnt: clean clean_docs clean_tex: rm -rf $(HTMLDIR)/* diff --git a/lib/erl_interface/src/Makefile b/lib/erl_interface/src/Makefile index 00c49f1622..6f728a0a28 100644 --- a/lib/erl_interface/src/Makefile +++ b/lib/erl_interface/src/Makefile @@ -33,3 +33,5 @@ endif clean depend docs release release_docs tests release_tests check xmllint: $(make_verbose)$(MAKE) -f $(TARGET)/Makefile $@ + +lcnt: diff --git a/lib/kernel/src/Makefile b/lib/kernel/src/Makefile index 88752431eb..2d2b84c206 100644 --- a/lib/kernel/src/Makefile +++ b/lib/kernel/src/Makefile @@ -67,6 +67,7 @@ MODULES = \ dist_ac \ dist_util \ erl_boot_server \ + erl_compile_server \ erl_ddll \ erl_distribution \ erl_epmd \ diff --git a/lib/kernel/src/erl_compile_server.erl b/lib/kernel/src/erl_compile_server.erl new file mode 100644 index 0000000000..f4b719068e --- /dev/null +++ b/lib/kernel/src/erl_compile_server.erl @@ -0,0 +1,253 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2019. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(erl_compile_server). +-behaviour(gen_server). +-export([start_link/0, compile/1]). + +%% Internal exports +-export([init/1, handle_call/3, handle_cast/2, handle_info/2]). + +-define(COMPILE_SERVER, erl_compile_server). +-define(IDLE_TIMEOUT, (10*1000)). +-define(WRONG_TIMEOUT, 1). + +-type client() :: {pid(), term()}. +-type job_map() :: #{{pid(),reference()} := client()}. + +-record(st, { + cwd=[] :: file:filename(), + config :: term(), + timeout=?IDLE_TIMEOUT :: non_neg_integer(), + jobs=#{} :: job_map() + }). + +-type state() :: #st{}. + +-spec start_link() -> {'ok', pid()} | {'error', term()}. + +start_link() -> + gen_server:start_link({local, ?COMPILE_SERVER}, ?MODULE, [], []). + +-spec init(Arg :: []) -> {'ok', any(), timeout()}. + +init([]) -> + %% We don't want the current directory in the code path. + %% Remove it. + Path = [D || D <- code:get_path(), D =/= "."], + true = code:set_path(Path), + Config = init_config(), + {ok, #st{config=Config}, ?IDLE_TIMEOUT}. + +-spec compile(term()) -> term(). + +compile(Parameters) -> + gen_server:call(?COMPILE_SERVER, {compile, Parameters}, infinity). + +-spec handle_call(any(), _, state()) -> {'noreply', state()}. + +handle_call({compile, Parameters}, From, #st{jobs=Jobs}=St0) -> + {ErlcArgs, PathArgs} = parse_command_line(Parameters), + case verify_context(PathArgs, Parameters, St0) of + {ok, St1} -> + #{cwd := Cwd, encoding := Enc} = Parameters, + PidRef = spawn_monitor(fun() -> exit(do_compile(ErlcArgs, Cwd, Enc)) end), + St = St1#st{jobs=Jobs#{PidRef => From}}, + {noreply, St#st{timeout=?IDLE_TIMEOUT}}; + wrong_config -> + case map_size(Jobs) of + 0 -> + %% Wrong configuration and no outstanding jobs. + %% Terminate immediately. + halt(); + _ -> + {reply, wrong_config, St0#st{timeout=?WRONG_TIMEOUT}, ?WRONG_TIMEOUT} + end + end. + +-spec handle_cast(term(), state()) -> {'noreply', state()}. + +handle_cast(_, St) -> + {noreply, St}. + +-spec handle_info(term(), state()) -> {'noreply', state(), timeout()}. + +handle_info({'DOWN',Ref,process,Pid,Reason}, #st{jobs=Jobs0}=St0) -> + Key = {Pid, Ref}, + Client = map_get(Key, Jobs0), + Jobs = maps:remove(Key, Jobs0), + St = St0#st{jobs=Jobs}, + gen_server:reply(Client, Reason), + case map_size(Jobs) =:= 0 of + true -> + {noreply, St, St#st.timeout}; + false -> + {noreply, St} + end; +handle_info(timeout, #st{jobs=Jobs}) when map_size(Jobs) =:= 0 -> + halt(); +handle_info(_, #st{timeout=Timeout}=St) -> + %% There are still outstanding jobs. + {noreply, St, Timeout}. + +%%% +%%% Local functions. +%%% + +verify_context(PathArgs, #{env := Env}=Parameters, St0) -> + case ensure_cwd(Parameters, St0) of + {ok, #st{config=Config}=St} -> + case make_config(PathArgs, Env) of + Config -> + {ok, St}; + _ -> + wrong_config + end; + wrong_config -> + wrong_config + end. + +ensure_cwd(#{cwd := Cwd}, #st{cwd=Cwd}=St) -> + {ok, St}; +ensure_cwd(#{cwd := NewCwd}, #st{jobs=Jobs}=St) when map_size(Jobs) =:= 0 -> + ok = file:set_cwd(NewCwd), + {ok, St#st{cwd=NewCwd}}; +ensure_cwd(#{}, #st{}) -> + wrong_config. + +do_compile(ErlcArgs, Cwd, Enc) -> + GL = create_gl(), + group_leader(GL, self()), + Result = erl_compile:compile(ErlcArgs, Cwd), + StdOutput = ensure_enc(gl_get_output(GL), Enc), + case Result of + ok -> + {ok, StdOutput}; + {error, StdErrorOutput0} -> + StdErrorOutput = ensure_enc(StdErrorOutput0, Enc), + {error, StdOutput, StdErrorOutput} + end. + +parse_command_line(#{command_line := CmdLine, cwd := Cwd}) -> + parse_command_line_1(CmdLine, Cwd, [], []). + +parse_command_line_1(["-pa", Pa|T], Cwd, PaAcc, PzAcc) -> + parse_command_line_1(T, Cwd, [Pa|PaAcc], PzAcc); +parse_command_line_1(["-pz", Pz|T], Cwd, PaAcc, PzAcc) -> + parse_command_line_1(T, Cwd, PaAcc, [Pz|PzAcc]); +parse_command_line_1(["-extra"|ErlcArgs], Cwd, PaAcc, PzAcc) -> + PaArgs = clean_path_args(lists:reverse(PaAcc), Cwd), + PzArgs = clean_path_args(lists:reverse(PzAcc), Cwd), + {ErlcArgs, [{pa, PaArgs}, {pz, PzArgs}]}; +parse_command_line_1([_|T], Cwd, PaAcc, PzAcc) -> + parse_command_line_1(T, Cwd, PaAcc, PzAcc). + +ensure_enc(Chars, latin1) -> + L = unicode:characters_to_list(Chars, unicode), + unicode:characters_to_binary( + [ case X of + High when High > 255 -> + ["\\x{", erlang:integer_to_list(X, 16), $}]; + Low -> + Low + end || X <- L ], unicode, latin1); +ensure_enc(Chars, _Enc) -> Chars. + +init_config() -> + EnvVars = ["ERL_AFLAGS", "ERL_FLAGS", "ERL_ZFLAGS", + "ERL_COMPILER_OPTIONS", + "ERL_LIBS", + "ERLC_CONFIGURATION"], + Env0 = [{Name, os:getenv(Name)} || Name <- EnvVars], + Env = [P || {_, Val}=P <- Env0, Val =/= false], + {ok, Cwd} = file:get_cwd(), + make_config([get_path_arg(pa, Cwd), get_path_arg(pz, Cwd)], Env). + +get_path_arg(PathArg, Cwd) -> + case init:get_argument(PathArg) of + error -> + {PathArg, []}; + {ok, Paths0} -> + Paths1 = lists:append(Paths0), + Paths = clean_path_args(Paths1, Cwd), + {PathArg, Paths} + end. + +clean_path_args(PathArgs, Cwd) -> + [filename:absname(P, Cwd) || P <- PathArgs]. + +make_config(PathArgs, Env0) -> + Env = lists:sort(Env0), + PathArgs ++ [iolist_to_binary([[Name,$=,Val,$\n] || {Name,Val} <- Env])]. + +%%% +%%% A group leader that will capture all output to the group leader. +%%% + +create_gl() -> + spawn_link(fun() -> gl_loop([]) end). + +gl_get_output(GL) -> + GL ! {self(), get_output}, + receive + {GL, Output} -> Output + end. + +gl_loop(State0) -> + receive + {io_request, From, ReplyAs, Request} -> + {_Tag, Reply, State} = gl_request(Request, State0), + gl_reply(From, ReplyAs, Reply), + gl_loop(State); + {From, get_output} -> + Output = iolist_to_binary(State0), + From ! {self(), Output}, + gl_loop(State0); + _Unknown -> + gl_loop(State0) + end. + +gl_reply(From, ReplyAs, Reply) -> + From ! {io_reply, ReplyAs, Reply}, + ok. + +gl_request({put_chars, Encoding, Chars}, State) -> + gl_put_chars(unicode:characters_to_binary(Chars, Encoding), State); +gl_request({put_chars, Encoding, Module, Function, Args}, State) -> + try + gl_request({put_chars, Encoding, apply(Module, Function, Args)}, State) + catch + _:_ -> + {{error,Function}, State} + end; +gl_request({requests, Reqs}, State) -> + gl_multi_request(Reqs, {ok, State}); +gl_request(_Other, State) -> + {error, {error, request}, State}. + +gl_multi_request([R|Rs], {ok, State}) -> + gl_multi_request(Rs, gl_request(R, State)); +gl_multi_request([_|_], Error) -> + Error; +gl_multi_request([], Result) -> + Result. + +gl_put_chars(Chars, Output) -> + {ok, ok, [Output,Chars]}. diff --git a/lib/kernel/src/kernel.app.src b/lib/kernel/src/kernel.app.src index c2ff6b63e9..4f5e6d782f 100644 --- a/lib/kernel/src/kernel.app.src +++ b/lib/kernel/src/kernel.app.src @@ -32,6 +32,7 @@ code_server, dist_util, erl_boot_server, + erl_compile_server, erl_distribution, erl_reply, erl_signal_handler, diff --git a/lib/kernel/src/kernel.erl b/lib/kernel/src/kernel.erl index c8c631ab23..8877ceea8e 100644 --- a/lib/kernel/src/kernel.erl +++ b/lib/kernel/src/kernel.erl @@ -166,10 +166,12 @@ init([]) -> modules => dynamic}, Timer = start_timer(), + CompileServer = start_compile_server(), {ok, {SupFlags, [Code, InetDb | DistChildren] ++ - [File, SigSrv, StdError, User, Config, RefC, SafeSup, LoggerSup] ++ Timer}} + [File, SigSrv, StdError, User, Config, RefC, SafeSup, LoggerSup] ++ + Timer ++ CompileServer}} end; init(safe) -> SupFlags = #{strategy => one_for_one, @@ -303,6 +305,19 @@ start_timer() -> [] end. +start_compile_server() -> + case application:get_env(kernel, start_compile_server) of + {ok, true} -> + [#{id => erl_compile_server, + start => {erl_compile_server, start_link, []}, + restart => permanent, + shutdown => 2000, + type => worker, + modules => [erl_compile_server]}]; + _ -> + [] + end. + %%----------------------------------------------------------------- %% The change of the distributed parameter is taken care of here %%----------------------------------------------------------------- diff --git a/lib/stdlib/doc/src/digraph.xml b/lib/stdlib/doc/src/digraph.xml index cf2c0844c9..fa6d2b81fe 100644 --- a/lib/stdlib/doc/src/digraph.xml +++ b/lib/stdlib/doc/src/digraph.xml @@ -36,10 +36,23 @@ <modulesummary>Directed graphs.</modulesummary> <description> <p>This module provides a version of labeled - directed graphs. What makes the graphs provided here - non-proper directed graphs is that multiple edges between - vertices are allowed. However, the customary definition of - directed graphs is used here.</p> + directed graphs ("digraphs").</p> + + <p>The digraphs managed by this module are stored in + <seealso marker="ets">ETS tables</seealso>. + That implies the following:</p> + + <list type="bulleted"> + <item><p>Only the process that created the digraph is allowed to update it.</p></item> + <item><p>Digraphs will not be garbage collected. The ETS tables used for a + digraph will only be deleted when <seealso marker="#delete/1"><c>delete/1</c></seealso> + is called or the process that created the digraph terminates.</p></item> + <item><p>A digraph is a mutable data structure.</p></item> + </list> + + <p>What makes the graphs provided here non-proper directed graphs + is that multiple edges between vertices are allowed. However, the + customary definition of directed graphs is used here.</p> <list type="bulleted"> <item> diff --git a/lib/stdlib/src/erl_compile.erl b/lib/stdlib/src/erl_compile.erl index f781312ca2..2a063fede1 100644 --- a/lib/stdlib/src/erl_compile.erl +++ b/lib/stdlib/src/erl_compile.erl @@ -22,12 +22,10 @@ -include("erl_compile.hrl"). -include("file.hrl"). --export([compile_cmdline/0]). +-export([compile_cmdline/0, compile/2]). -export_type([cmd_line_arg/0]). --define(STDERR, standard_error). %Macro to avoid misspellings. - %% Mapping from extension to {M,F} to run the correct compiler. compiler(".erl") -> {compile, compile}; @@ -46,60 +44,56 @@ compiler(".asn") -> {asn1ct, compile_asn}; compiler(".py") -> {asn1ct, compile_py}; compiler(_) -> no. -%% Entry from command line. - -type cmd_line_arg() :: atom() | string(). +%% Run a compilation based on the command line arguments and then halt. +%% Intended for one-off compilation by erlc. -spec compile_cmdline() -> no_return(). - compile_cmdline() -> + cmdline_init(), List = init:get_plain_arguments(), - case compile(List) of - ok -> my_halt(0); - error -> my_halt(1); - _ -> my_halt(2) + compile_cmdline1(List). + +%% Run a compilation. Meant to be used by the compilation server. +-spec compile(list(), file:filename()) -> 'ok' | {'error', binary()}. +compile(Args, Cwd) -> + try compile1(Args, #options{outdir=Cwd,cwd=Cwd}) of + ok -> + ok + catch + throw:{error, Output} -> + {error, unicode:characters_to_binary(Output)}; + C:E:Stk -> + {crash, {C,E,Stk}} end. --spec my_halt(_) -> no_return(). -my_halt(Reason) -> - erlang:halt(Reason). - -%% Run the the compiler in a separate process, trapping EXITs. - -compile(List) -> - process_flag(trap_exit, true), - Pid = spawn_link(compiler_runner(List)), +%% Run the the compiler in a separate process. +compile_cmdline1(Args) -> + {ok, Cwd} = file:get_cwd(), + {Pid,Ref} = spawn_monitor(fun() -> exit(compile(Args, Cwd)) end), receive - {'EXIT', Pid, {compiler_result, Result}} -> - Result; - {'EXIT', Pid, {compiler_error, Error}} -> - io:put_chars(?STDERR, Error), - io:nl(?STDERR), - error; - {'EXIT', Pid, Reason} -> - io:format(?STDERR, "Runtime error: ~tp~n", [Reason]), - error + {'DOWN', Ref, process, Pid, Result} -> + case Result of + ok -> + halt(0); + {error, Output} -> + io:put_chars(standard_error, Output), + halt(1); + {crash, {C,E,Stk}} -> + io:format(standard_error, "Crash: ~p:~tp\n~tp\n", + [C,E,Stk]), + halt(2) + end end. --spec compiler_runner([cmd_line_arg()]) -> fun(() -> no_return()). - -compiler_runner(List) -> - fun() -> - %% We don't want the current directory in the code path. - %% Remove it. - Path = [D || D <- code:get_path(), D =/= "."], - true = code:set_path(Path), - exit({compiler_result, compile1(List)}) - end. - -%% Parses the first part of the option list. - -compile1(Args) -> - {ok, Cwd} = file:get_cwd(), - compile1(Args, #options{outdir=Cwd,cwd=Cwd}). - -%% Parses all options. +cmdline_init() -> + %% We don't want the current directory in the code path. + %% Remove it. + Path = [D || D <- code:get_path(), D =/= "."], + true = code:set_path(Path), + ok. +%% Parse all options. compile1(["--"|Files], Opts) -> compile2(Files, Opts); compile1(["-"++Option|T], Opts) -> @@ -132,12 +126,8 @@ parse_generic_option("I"++Opt, T0, #options{cwd=Cwd}=Opts) -> AbsDir = filename:absname(Dir, Cwd), compile1(T, Opts#options{includes=[AbsDir|Opts#options.includes]}); parse_generic_option("M"++Opt, T0, #options{specific=Spec}=Opts) -> - case parse_dep_option(Opt, T0) of - error -> - error; - {SpecOpts,T} -> - compile1(T, Opts#options{specific=SpecOpts++Spec}) - end; + {SpecOpts,T} = parse_dep_option(Opt, T0), + compile1(T, Opts#options{specific=SpecOpts++Spec}); parse_generic_option("o"++Opt, T0, #options{cwd=Cwd}=Opts) -> {Dir,T} = get_option("o", Opt, T0), AbsName = filename:absname(Dir, Cwd), @@ -181,8 +171,7 @@ parse_generic_option("P", T, #options{specific=Spec}=Opts) -> parse_generic_option("S", T, #options{specific=Spec}=Opts) -> compile1(T, Opts#options{specific=['S'|Spec]}); parse_generic_option(Option, _T, _Opts) -> - io:format(?STDERR, "Unknown option: -~ts\n", [Option]), - usage(). + usage(io_lib:format("Unknown option: -~ts\n", [Option])). parse_dep_option("", T) -> {[makedep,{makedep_output,standard_io}],T}; @@ -204,10 +193,14 @@ parse_dep_option("T"++Opt, T0) -> {Target,T} = get_option("MT", Opt, T0), {[{makedep_target,Target}],T}; parse_dep_option(Opt, _T) -> - io:format(?STDERR, "Unknown option: -M~ts\n", [Opt]), - usage(). + usage(io_lib:format("Unknown option: -M~ts\n", [Opt])). + +-spec usage() -> no_return(). usage() -> + usage(""). + +usage(Error) -> H = [{"-b type","type of output file (e.g. beam)"}, {"-d","turn on debugging of erlc itself"}, {"-Dname","define name"}, @@ -238,18 +231,18 @@ usage() -> {"-S","generate assembly listing (Erlang compiler)"}, {"-P","generate listing of preprocessed code (Erlang compiler)"}, {"+term","pass the Erlang term unchanged to the compiler"}], - io:put_chars(?STDERR, - ["Usage: erlc [Options] file.ext ...\n", - "Options:\n", - [io_lib:format("~-14s ~s\n", [K,D]) || {K,D} <- H]]), - error. + Msg = [Error, + "Usage: erlc [Options] file.ext ...\n", + "Options:\n", + [io_lib:format("~-14s ~s\n", [K,D]) || {K,D} <- H]], + throw({error, Msg}). get_option(_Name, [], [[C|_]=Option|T]) when C =/= $- -> {Option,T}; get_option(_Name, [_|_]=Option, T) -> {Option,T}; get_option(Name, _, _) -> - exit({compiler_error,"No value given to -"++Name++" option"}). + throw({error, "No value given to -"++Name++" option\n"}). split_at_equals([$=|T], Acc) -> {lists:reverse(Acc),T}; @@ -266,14 +259,10 @@ compile2(Files, #options{cwd=Cwd,includes=Incl,outfile=Outfile}=Opts0) -> {[_|_], 1} -> compile3(Files, Cwd, Opts); {[_|_], _N} -> - io:put_chars(?STDERR, - "Output file name given, " - "but more than one input file.\n"), - error + throw({error, "Output file name given, but more than one input file.\n"}) end. -%% Compiles the list of files, until done or compilation fails. - +%% Compile the list of files, until done or compilation fails. compile3([File|Rest], Cwd, Options) -> Ext = filename:extension(File), Root = filename:rootname(File), @@ -285,43 +274,37 @@ compile3([File|Rest], Cwd, Options) -> Outfile -> filename:rootname(Outfile) end, - case compile_file(Ext, InFile, OutFile, Options) of - ok -> - compile3(Rest, Cwd, Options); - Other -> - Other - end; + compile_file(Ext, InFile, OutFile, Options), + compile3(Rest, Cwd, Options); compile3([], _Cwd, _Options) -> ok. -%% Invokes the appropriate compiler, depending on the file extension. - +%% Invoke the appropriate compiler, depending on the file extension. compile_file("", Input, _Output, _Options) -> - io:format(?STDERR, "File has no extension: ~ts~n", [Input]), - error; + throw({error, io_lib:format("File has no extension: ~ts~n", [Input])}); compile_file(Ext, Input, Output, Options) -> case compiler(Ext) of no -> - io:format(?STDERR, "Unknown extension: '~ts'\n", [Ext]), - error; + Error = io_lib:format("Unknown extension: '~ts'\n", [Ext]), + throw({error, Error}); {M, F} -> - case catch M:F(Input, Output, Options) of - ok -> ok; - error -> error; - {'EXIT',Reason} -> - io:format(?STDERR, - "Compiler function ~w:~w/3 failed:\n~p~n", - [M,F,Reason]), - error; + try M:F(Input, Output, Options) of + ok -> + ok; + error -> + throw({error, ""}); Other -> - io:format(?STDERR, - "Compiler function ~w:~w/3 returned:\n~p~n", - [M,F,Other]), - error + Error = io_lib:format("Compiler function ~w:~w/3 returned:\n~tp~n", + [M,F,Other]), + throw({error, Error}) + catch + throw:Reason:Stk -> + Error = io_lib:format("Compiler function ~w:~w/3 failed:\n~tp\n~tp\n", + [M,F,Reason,Stk]), + throw({error, Error}) end end. -%% Guesses if a give name refers to a file or a directory. - +%% Guess whether a given name refers to a file or a directory. file_or_directory(Name) -> case file:read_file_info(Name) of {ok, #file_info{type=regular}} -> @@ -335,18 +318,16 @@ file_or_directory(Name) -> end end. -%% Makes an Erlang term given a string. - -make_term(Str) -> +%% Make an Erlang term given a string. +make_term(Str) -> case erl_scan:string(Str) of - {ok, Tokens, _} -> + {ok, Tokens, _} -> case erl_parse:parse_term(Tokens ++ [{dot, erl_anno:new(1)}]) of - {ok, Term} -> Term; + {ok, Term} -> + Term; {error, {_,_,Reason}} -> - io:format(?STDERR, "~ts: ~ts~n", [Reason, Str]), - throw(error) + throw({error, io_lib:format("~ts: ~ts~n", [Reason, Str])}) end; {error, {_,_,Reason}, _} -> - io:format(?STDERR, "~ts: ~ts~n", [Reason, Str]), - throw(error) + throw({error, io_lib:format("~ts: ~ts~n", [Reason, Str])}) end. diff --git a/scripts/build-otp b/scripts/build-otp index 55023ba7d8..afab1f9136 100755 --- a/scripts/build-otp +++ b/scripts/build-otp @@ -42,6 +42,7 @@ if [ ! -d "logs" ]; then mkdir logs fi +export ERLC_USE_SERVER=yes do_and_log "Autoconfing" ./otp_build autoconf do_and_log "Configuring" ./otp_build configure echo Configure result: |