summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.in11
-rw-r--r--bootstrap/lib/kernel/ebin/erl_compile_server.beambin0 -> 4928 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/kernel.app1
-rw-r--r--bootstrap/lib/kernel/ebin/kernel.beambin3624 -> 3744 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_compile.beambin6624 -> 6756 bytes
-rw-r--r--erts/doc/src/erlc.xml63
-rw-r--r--erts/emulator/Makefile.in4
-rw-r--r--erts/etc/common/Makefile.in12
-rw-r--r--erts/etc/common/erlc.c544
-rw-r--r--erts/test/erlc_SUITE.erl23
-rw-r--r--lib/erl_interface/doc/src/Makefile2
-rw-r--r--lib/erl_interface/src/Makefile2
-rw-r--r--lib/kernel/src/Makefile1
-rw-r--r--lib/kernel/src/erl_compile_server.erl253
-rw-r--r--lib/kernel/src/kernel.app.src1
-rw-r--r--lib/kernel/src/kernel.erl17
-rw-r--r--lib/stdlib/doc/src/digraph.xml21
-rw-r--r--lib/stdlib/src/erl_compile.erl187
-rwxr-xr-xscripts/build-otp1
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
new file mode 100644
index 0000000000..43c77c4d7e
--- /dev/null
+++ b/bootstrap/lib/kernel/ebin/erl_compile_server.beam
Binary files differ
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
index 7e653a8d5b..78c073ed5c 100644
--- a/bootstrap/lib/kernel/ebin/kernel.beam
+++ b/bootstrap/lib/kernel/ebin/kernel.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_compile.beam b/bootstrap/lib/stdlib/ebin/erl_compile.beam
index 2f5790b4a7..ca55b187e8 100644
--- a/bootstrap/lib/stdlib/ebin/erl_compile.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_compile.beam
Binary files differ
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: