mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-12 04:14:06 +00:00
Estimated hours taken: 50 Branches: main Remove Unix dependencies in the compiler. Avoid calling passes_aux.invoke_shell_command, which requires the presence of a Unix shell. The implementation of fact tables still has dependencies on Unix utilities (e.g. sort). aclocal.m4: Don't pass Unix style paths to MSVC. configure.in: Use `cygpath -m' rather than `cygpath -w'. `cygpath -m' uses '/' as the directory separator, so it doesn't cause quoting problems in shell scripts. Apply $CYGPATH to $PREFIX, $LIBDIR, $CONFIG_PREFIX and $CONFIG_LIBDIR. Don't pass `-lm' when linking with MSVC. configure.in: compiler/options.m: scripts/Mercury.config.in: Add extra configuration options to deal with differences between linking with gcc and MSVC: --linker-opt-separator --linker-link-lib-flag --linker-link-lib-suffix --shlib-linker-link-lib-flag --shlib-linker-link-lib-suffix --linker-path-flag NEWS: doc/user_guide.texi: compiler/options.m: compiler/compile_target_code.m: compiler/make.program_target.m: Instead of substituting in an arbitrary shell script when processing `--pre-link-command' and `--extra-init-command', require that these options specify a command which will be passed the name of the source file containing the main module as the first argument, with the source files containing the remaining modules following. This is simpler and avoids dependencies on a shell. Fix quote_arg to handle Windows paths better. compiler/handle_options.m: Don't attempt to use symlinks if they're not available. compiler/compile_target_code.m: Be more careful about quoting. Don't call invoke_shell_command where invoke_system_command would do. Allow linking using MSVC. compiler/modules.m: Remove make_directory, which is now implemented by dir.m. Use io.make_symlink rather than shell scripts. Implement mercury_update_interface in Mercury. compiler/llds_out.m: compiler/make.program_target.m: Use dir.make_directory, not modules.make_directory, which has been removed. compiler/make.module_target.m: Invoke mercury_compiler directly, not through the mmc script to avoid shell dependencies. If we can't fork() child `mmc --make' processes, pass the arguments to the child process using a file to avoid overflowing system limits on Windows. compiler/mercury_compile.m: compiler/options_file.m: Read argument files. Handle backslash-newline in options files correctly. compiler/passes_aux.m: invoke_system_command shouldn't set the exit status -- the caller may be able to try something else. compiler/process_util.m: Export can_fork for use by make.module_target.m. Remove hacks to work around bugs in the implementation of zero-arity foreign procs. compiler/prog_io.m: Handle bizarre file names without aborting. library/Mmakefile: library/print_extra_inits: Move code to find extra initialization functions into print_extra_inits, due to the change to the handling of the --extra-init-command option described above. scripts/mmc.in: Set the MERCURY_COMPILER environment variable if it is not already set, so that the mercury_compile executable knows where to find itself. scripts/mercury.bat.in: Make this actually work. tools/bootcheck: Set ANALYSIS_LIB_NAME. Apply cygpath (-m not -w) to $root. Link print_extra_inits into the stage2 and stage3 library directories. util/mkinit.c: Handle '\\' in path names.
398 lines
11 KiB
Mathematica
398 lines
11 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 2002-2003 University of Melbourne.
|
|
% This file may only be copied under the terms of the GNU General
|
|
% Public License - see the file COPYING in the Mercury distribution.
|
|
%-----------------------------------------------------------------------------%
|
|
% File: process_util.m
|
|
% Main author: stayl
|
|
%
|
|
% Process and signal handling, mainly for use by make.m and its sub-modules.
|
|
%-----------------------------------------------------------------------------%
|
|
:- module libs__process_util.
|
|
|
|
:- interface.
|
|
|
|
:- import_module bool, io.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type build0(Info) == pred(bool, Info, Info, io__state, io__state).
|
|
:- inst build0 == (pred(out, in, out, di, uo) is det).
|
|
|
|
:- type post_signal_cleanup(Info) == pred(Info, Info, io__state, io__state).
|
|
:- inst post_signal_cleanup == (pred(in, out, di, uo) is det).
|
|
|
|
% build_with_check_for_interrupt(Build, Cleanup,
|
|
% Succeeded, Info0, Info)
|
|
%
|
|
% Apply `Build' with signal handlers installed to check for signals
|
|
% which would normally kill the process. If a signal occurs call
|
|
% `Cleanup', then restore signal handlers to their defaults and
|
|
% reraise the signal to kill the current process.
|
|
% An action being performed in a child process by
|
|
% call_in_forked_process will be killed if a fatal signal
|
|
% (SIGINT, SIGTERM, SIGHUP or SIGQUIT) is received by the
|
|
% current process.
|
|
% An action being performed within the current process or by
|
|
% system() will run to completion, with the interrupt being taken
|
|
% immediately afterwards.
|
|
:- pred build_with_check_for_interrupt(build0(Info)::in(build0),
|
|
post_signal_cleanup(Info)::in(post_signal_cleanup), bool::out,
|
|
Info::in, Info::out, io__state::di, io__state::uo) is det.
|
|
|
|
% raise_signal(Signal).
|
|
% Send `Signal' to the current process.
|
|
:- pred raise_signal(int::in, io__state::di, io__state::uo) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type io_pred == pred(bool, io__state, io__state).
|
|
:- inst io_pred == (pred(out, di, uo) is det).
|
|
|
|
% Does fork() work on the current platform.
|
|
:- pred can_fork is semidet.
|
|
|
|
% call_in_forked_process(P, AltP, Succeeded)
|
|
%
|
|
% Execute `P' in a separate process.
|
|
%
|
|
% We prefer to use fork() rather than system() because
|
|
% that will avoid shell and Mercury runtime startup overhead.
|
|
% Interrupt handling will also work better (system() on Linux
|
|
% ignores SIGINT).
|
|
%
|
|
% If fork() is not supported on the current architecture,
|
|
% `AltP' will be called instead in the current process.
|
|
:- pred call_in_forked_process(io_pred::in(io_pred), io_pred::in(io_pred),
|
|
bool::out, io__state::di, io__state::uo) is det.
|
|
|
|
% As above, but if fork() is not available, just call the
|
|
% predicate in the current process.
|
|
:- pred call_in_forked_process(io_pred::in(io_pred),
|
|
bool::out, io__state::di, io__state::uo) is det.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module libs__globals.
|
|
:- import_module libs__options.
|
|
|
|
:- import_module std_util, require.
|
|
|
|
build_with_check_for_interrupt(Build, Cleanup, Succeeded, Info0, Info) -->
|
|
setup_signal_handlers(MaybeSigIntHandler),
|
|
Build(Succeeded0, Info0, Info1),
|
|
restore_signal_handlers(MaybeSigIntHandler),
|
|
check_for_signal(Signalled, Signal),
|
|
( { Signalled = 1 } ->
|
|
{ Succeeded = no },
|
|
globals__io_lookup_bool_option(verbose_make, Verbose),
|
|
( { Verbose = yes } ->
|
|
io__write_string("** Received signal "),
|
|
io__write_int(Signal),
|
|
io__write_string(", cleaning up.\n")
|
|
;
|
|
[]
|
|
),
|
|
Cleanup(Info1, Info),
|
|
|
|
% The signal handler has been restored to the default,
|
|
% so this should kill us.
|
|
raise_signal(Signal)
|
|
;
|
|
{ Succeeded = Succeeded0 },
|
|
{ Info = Info1 }
|
|
).
|
|
|
|
:- type signal_action ---> signal_action.
|
|
:- pragma foreign_type("C", signal_action, "MR_signal_action").
|
|
|
|
:- pragma foreign_decl("C",
|
|
"
|
|
#ifdef MR_HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef MR_HAVE_SYS_TYPES_H
|
|
#include <sys/types.h>
|
|
#endif
|
|
|
|
#ifdef MR_HAVE_SYS_WAIT_H
|
|
#include <sys/wait.h>
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
|
|
#include ""mercury_signal.h""
|
|
#include ""mercury_types.h""
|
|
#include ""mercury_heap.h""
|
|
#include ""mercury_misc.h""
|
|
|
|
#if defined(MR_HAVE_FORK) && defined(MR_HAVE_WAIT) && defined(MR_HAVE_KILL)
|
|
#define MC_CAN_FORK 1
|
|
#endif
|
|
|
|
#define MC_SETUP_SIGNAL_HANDLER(sig, handler) \
|
|
MR_setup_signal(sig, (MR_Code *) handler, MR_FALSE, \
|
|
""mercury_compile: cannot install signal handler"");
|
|
|
|
/* Have we received a signal. */
|
|
extern volatile sig_atomic_t MC_signalled;
|
|
|
|
/*
|
|
** Which signal did we receive.
|
|
** XXX This assumes a signal number will fit into a sig_atomic_t.
|
|
*/
|
|
extern volatile sig_atomic_t MC_signal_received;
|
|
|
|
void MC_mercury_compile_signal_handler(int sig);
|
|
").
|
|
|
|
:- pragma foreign_code("C",
|
|
"
|
|
volatile sig_atomic_t MC_signalled = MR_FALSE;
|
|
volatile sig_atomic_t MC_signal_received = 0;
|
|
|
|
void
|
|
MC_mercury_compile_signal_handler(int sig)
|
|
{
|
|
MC_signalled = MR_TRUE;
|
|
MC_signal_received = sig;
|
|
}
|
|
").
|
|
|
|
:- pred setup_signal_handlers(signal_action::out,
|
|
io__state::di, io__state::uo) is det.
|
|
|
|
setup_signal_handlers(signal_action::out, IO::di, IO::uo).
|
|
|
|
:- pragma foreign_proc("C",
|
|
setup_signal_handlers(SigintHandler::out, IO0::di, IO::uo),
|
|
[will_not_call_mercury, promise_pure, tabled_for_io],
|
|
"{
|
|
IO = IO0;
|
|
MC_signalled = MR_FALSE;
|
|
|
|
/*
|
|
** mdb sets up a SIGINT handler, so we should restore
|
|
** it after we're done.
|
|
*/
|
|
MR_get_signal_action(SIGINT, &SigintHandler,
|
|
""error getting SIGINT handler"");
|
|
MC_SETUP_SIGNAL_HANDLER(SIGINT, MC_mercury_compile_signal_handler);
|
|
MC_SETUP_SIGNAL_HANDLER(SIGTERM, MC_mercury_compile_signal_handler);
|
|
#ifdef SIGHUP
|
|
MC_SETUP_SIGNAL_HANDLER(SIGHUP, MC_mercury_compile_signal_handler);
|
|
#endif
|
|
#ifdef SIGQUIT
|
|
MC_SETUP_SIGNAL_HANDLER(SIGQUIT, MC_mercury_compile_signal_handler);
|
|
#endif
|
|
}").
|
|
|
|
:- pred restore_signal_handlers(signal_action::in,
|
|
io__state::di, io__state::uo) is det.
|
|
|
|
restore_signal_handlers(_::in, IO::di, IO::uo).
|
|
|
|
:- pragma foreign_proc("C",
|
|
restore_signal_handlers(SigintHandler::in, IO0::di, IO::uo),
|
|
[will_not_call_mercury, promise_pure, tabled_for_io],
|
|
"{
|
|
IO = IO0;
|
|
MR_set_signal_action(SIGINT, &SigintHandler,
|
|
""error resetting SIGINT handler"");
|
|
MC_SETUP_SIGNAL_HANDLER(SIGTERM, SIG_DFL);
|
|
#ifdef SIGHUP
|
|
MC_SETUP_SIGNAL_HANDLER(SIGHUP, SIG_DFL);
|
|
#endif
|
|
#ifdef SIGQUIT
|
|
MC_SETUP_SIGNAL_HANDLER(SIGQUIT, SIG_DFL);
|
|
#endif
|
|
}").
|
|
|
|
% Restore all signal handlers to default values in the child
|
|
% so that the child will be killed by the signals the parent
|
|
% is catching.
|
|
:- pred setup_child_signal_handlers(io__state::di, io__state::uo) is det.
|
|
|
|
setup_child_signal_handlers -->
|
|
restore_signal_handlers(sig_dfl).
|
|
|
|
:- func sig_dfl = signal_action.
|
|
|
|
sig_dfl = (signal_action::out).
|
|
|
|
:- pragma foreign_proc("C", sig_dfl = (Result::out),
|
|
[will_not_call_mercury, promise_pure],
|
|
"MR_init_signal_action(&Result, SIG_DFL, MR_FALSE, MR_TRUE);").
|
|
|
|
:- pred check_for_signal(int::out, int::out,
|
|
io__state::di, io__state::uo) is det.
|
|
|
|
check_for_signal(0::out, 0::out, IO::di, IO::uo).
|
|
|
|
:- pragma foreign_proc("C",
|
|
check_for_signal(Signalled::out, Signal::out, IO0::di, IO::uo),
|
|
[will_not_call_mercury, promise_pure, tabled_for_io],
|
|
"
|
|
IO = IO0;
|
|
Signalled = (MC_signalled ? 1 : 0);
|
|
Signal = MC_signal_received;
|
|
").
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- pragma foreign_decl("C", "#include <signal.h>").
|
|
|
|
% If this aborted it would cause partially built files
|
|
% to be left lying around with `--make'.
|
|
raise_signal(_::in, IO::di, IO::uo).
|
|
|
|
:- pragma foreign_proc("C",
|
|
raise_signal(Signal::in, IO0::di, IO::uo),
|
|
[will_not_call_mercury, promise_pure, tabled_for_io],
|
|
"
|
|
IO = IO0;
|
|
raise(Signal);
|
|
").
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
call_in_forked_process(P, Success) -->
|
|
call_in_forked_process(P, P, Success).
|
|
|
|
call_in_forked_process(P, AltP, Success) -->
|
|
( { can_fork } ->
|
|
call_in_forked_process_2(P, ForkStatus, CallStatus),
|
|
{ ForkStatus = 1 ->
|
|
Success = no
|
|
;
|
|
Status = io__handle_system_command_exit_status(
|
|
CallStatus),
|
|
Success = (Status = ok(exited(0)) -> yes ; no)
|
|
}
|
|
;
|
|
AltP(Success)
|
|
).
|
|
|
|
can_fork :- semidet_fail.
|
|
|
|
:- pragma foreign_proc("C", can_fork,
|
|
[will_not_call_mercury, thread_safe, promise_pure],
|
|
"
|
|
#ifdef MC_CAN_FORK
|
|
SUCCESS_INDICATOR = MR_TRUE;
|
|
#else
|
|
SUCCESS_INDICATOR = MR_FALSE;
|
|
#endif
|
|
").
|
|
|
|
:- pred call_in_forked_process_2(io_pred::in(io_pred), int::out, int::out,
|
|
io__state::di, io__state::uo) is det.
|
|
|
|
call_in_forked_process_2(_::in(io_pred), _::out, _::out, _::di, _::uo) :-
|
|
error("call_in_forked_process_2").
|
|
|
|
:- pragma foreign_proc("C",
|
|
call_in_forked_process_2(Pred::in(io_pred),
|
|
ForkStatus::out, Status::out, IO0::di, IO::uo),
|
|
[may_call_mercury, promise_pure, tabled_for_io],
|
|
"{
|
|
#ifdef MC_CAN_FORK
|
|
pid_t child_pid;
|
|
|
|
IO = IO0;
|
|
ForkStatus = 0;
|
|
Status = 0;
|
|
|
|
child_pid = fork();
|
|
if (child_pid == -1) { /* error */
|
|
MR_perror(""error in fork()"");
|
|
ForkStatus = 1;
|
|
} else if (child_pid == 0) { /* child */
|
|
MR_Integer exit_status;
|
|
|
|
MC_call_child_process_io_pred(Pred, &exit_status);
|
|
exit(exit_status);
|
|
} else { /* parent */
|
|
int child_status;
|
|
pid_t wait_status;
|
|
|
|
/*
|
|
** Make sure the wait() is interrupted by the signals
|
|
** which cause us to exit.
|
|
*/
|
|
MR_signal_should_restart(SIGINT, MR_FALSE);
|
|
MR_signal_should_restart(SIGTERM, MR_FALSE);
|
|
#ifdef SIGHUP
|
|
MR_signal_should_restart(SIGHUP, MR_FALSE);
|
|
#endif
|
|
#ifdef SIGQUIT
|
|
MR_signal_should_restart(SIGQUIT, MR_FALSE);
|
|
#endif
|
|
|
|
while (1) {
|
|
wait_status = wait(&child_status);
|
|
if (wait_status == child_pid) {
|
|
Status = child_status;
|
|
break;
|
|
} else if (wait_status == -1) {
|
|
if (MR_is_eintr(errno)) {
|
|
if (MC_signalled) {
|
|
/*
|
|
** A normally fatal signal has been received,
|
|
** so kill the child immediately.
|
|
** Use SIGTERM, not MC_signal_received,
|
|
** because the child may be inside a call
|
|
** to system() which would cause SIGINT
|
|
** to be ignored on some systems (e.g. Linux).
|
|
*/
|
|
kill(child_pid, SIGTERM);
|
|
break;
|
|
}
|
|
} else {
|
|
/*
|
|
** This should never happen.
|
|
*/
|
|
MR_perror(""error in wait(): "");
|
|
ForkStatus = 1;
|
|
Status = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Restore the system call signal behaviour.
|
|
*/
|
|
MR_signal_should_restart(SIGINT, MR_TRUE);
|
|
MR_signal_should_restart(SIGTERM, MR_TRUE);
|
|
#ifdef SIGHUP
|
|
MR_signal_should_restart(SIGHUP, MR_TRUE);
|
|
#endif
|
|
#ifdef SIGQUIT
|
|
MR_signal_should_restart(SIGQUIT, MR_TRUE);
|
|
#endif
|
|
|
|
}
|
|
#else /* ! MC_CAN_FORK */
|
|
IO = IO0;
|
|
ForkStatus = 1;
|
|
Status = 1;
|
|
#endif /* ! MC_CAN_FORK */
|
|
}").
|
|
|
|
% call_child_process_io_pred(P, ExitStatus).
|
|
:- pred call_child_process_io_pred(io_pred::in(io_pred), int::out,
|
|
io__state::di, io__state::uo) is det.
|
|
:- pragma export(call_child_process_io_pred(in(io_pred), out, di, uo),
|
|
"MC_call_child_process_io_pred").
|
|
|
|
call_child_process_io_pred(P, Status) -->
|
|
setup_child_signal_handlers,
|
|
P(Success),
|
|
{ Status = ( Success = yes -> 0 ; 1 ) }.
|
|
|
|
%-----------------------------------------------------------------------------%
|