Files
mercury/compiler/make.util.m
Zoltan Somogyi d3028aa52e Don't try to remove directories for mmc --make *.clean.
compiler/make.util.m:
    All callers of the remove_file_for_make predicate pass it
    the names of *files*, not directories, to remove. So stop invoking
    a library predicate intended to remove complete directort hierarchies.

library/io.file.m:
    Document the lack of a predicate that removes *only* regular files
    as a problem. (Even io.file.remove_file can, and usually will, remove
    the named "file" if it is in fact a directory, provided it is empty.)

    Make the descriptions of the exported predicates of this module
    easier to read.

    Make a foreign_proc more readable as well.
2025-09-30 17:11:33 +10:00

533 lines
18 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2002-2012 The University of Melbourne.
% Copyright (C) 2013-2025 The Mercury team.
% This file may only be copied under the terms of the GNU General
% Public License - see the file COPYING in the Mercury distribution.
%---------------------------------------------------------------------------%
%
% File: make.util.m.
% Authors: stayl, wangp.
%
% Assorted predicates used to implement `mmc --make'.
%
%---------------------------------------------------------------------------%
:- module make.util.
:- interface.
:- import_module libs.
:- import_module libs.file_util.
:- import_module libs.globals.
:- import_module libs.maybe_util.
:- import_module libs.options.
:- import_module make.make_info.
:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.
:- import_module parse_tree.file_names.
:- import_module io.
:- import_module list.
%---------------------------------------------------------------------------%
%
% Remove file a file, deleting the cached timestamp.
% The removal is reported to the user if the given boolean option is set.
% In general the option given should be `--very-verbose' when making a
% `.clean' or `.realclean target', and `--verbose-make' when cleaning
% after an interrupted build.
%
% Remove the target file and the corresponding timestamp file.
%
:- pred remove_make_target_file(io.text_output_stream::in, globals::in,
string::in, option::in, target_file::in,
make_info::in, make_info::out, io::di, io::uo) is det.
% Remove the target file and the corresponding timestamp file.
%
:- pred remove_make_target_file_by_name(io.text_output_stream::in, globals::in,
string::in, option::in, module_name::in, module_target_type::in,
make_info::in, make_info::out, io::di, io::uo) is det.
% remove_module_file_for_make(ProgressStream, Globals, VerboseOption,
% ModuleName, Extension, !Info, !IO).
%
:- pred remove_module_file_for_make(io.text_output_stream::in, globals::in,
option::in, module_name::in, ext::in, make_info::in, make_info::out,
io::di, io::uo) is det.
:- pred remove_file_for_make(io.text_output_stream::in, globals::in,
option::in, file_name::in, make_info::in, make_info::out,
io::di, io::uo) is det.
%---------------------------------------------------------------------------%
:- func make_target_file_list(list(module_name), module_target_type) =
list(target_file).
:- func make_target_id_list(list(module_name), module_target_type)
= list(target_id).
%---------------------------------------------------------------------------%
:- type maybe_grade_dependent
---> not_grade_dependent
; grade_dependent.
% Test whether targets of a given type are grade-dependent or not.
% NOTE: some grade-dependent target types are also dependent on the
% ISA (instruction set architecture) of the target machine, while
% some are not. However, grade-independent target types are never
% architecture-dependent.
%
:- func is_target_grade_dependent(module_target_type) = maybe_grade_dependent.
%---------------------------------------------------------------------------%
:- type stop_or_continue
---> soc_stop
; soc_continue.
:- pred should_we_stop_or_continue(maybe_keep_going::in, maybe_succeeded::in,
stop_or_continue::out, maybe_succeeded::in, maybe_succeeded::out) is det.
%---------------------------------------------------------------------------%
%
% Debugging, progress, and error messages.
%
:- pred debug_make_msg(globals::in, pred(string)::in(pred(out) is det),
string::out) is det.
%---------------------%
% verbose_make_N_part_msg(Globals, Part1, ..., Msg):
%
% If `--verbose-make' is set, return a message consisting of the
% given input strings (with spaces between them) and a newline.
% Otherwise, return the empty string.
%
:- pred verbose_make_one_part_msg(globals::in,
string::in, string::out) is det.
:- pred verbose_make_two_part_msg(globals::in,
string::in, string::in, string::out) is det.
:- pred verbose_make_three_part_msg(globals::in,
string::in, string::in, string::in, string::out) is det.
:- pred verbose_make_four_part_msg(globals::in,
string::in, string::in, string::in, string::in, string::out) is det.
% option_set_N_part_msg(Globals, Option, Part1, ..., Msg):
%
% If the given option is set, return a message consisting of the
% given input strings (with spaces between them) and a newline.
% Otherwise, return the empty string.
%
:- pred option_set_one_part_msg(globals::in, option::in,
string::in, string::out) is det.
:- pred option_set_two_part_msg(globals::in, option::in,
string::in, string::in, string::out) is det.
:- pred option_set_three_part_msg(globals::in, option::in,
string::in, string::in, string::in, string::out) is det.
:- pred option_set_four_part_msg(globals::in, option::in,
string::in, string::in, string::in, string::in, string::out) is det.
% If `--verbose-make' is set, return a progress message saying that
% the compiler is "Making <filename>". Otherwise, return the empty string.
%
:- pred maybe_making_filename_msg(globals::in, file_name::in,
string::out) is det.
% If `--verbose-make' is set, return a progress message saying that
% the compiler is reanalysing invalid/suboptimal modules.
% Otherwise, return the empty string.
%
:- pred maybe_reanalyse_modules_msg(globals::in, string::out) is det.
% Return an error nessage saying there was an error making the given
% target, which has the given filename.
%
:- pred file_error_msg(file_name::in, string::out) is det.
% If
% - the warn_up_to_date option is set, and
% - the given target was specified on the command line, and
% - the given targe has not yet been deleted from that list of
% command line targets,
% then return a warning about it already being up to date.
% Otherwise, return the empty string. In both cases, this predicate
% will delete ths given target from the list of command line targets,
% which prevents duplicate "up to date" messages.
%
% XXX The list of command line targets is never used for any purpose
% other than this last test. I (zs) am not sure that it is actually useful
% for this purpose, because
%
% - the two places other than maybe_warn_up_to_date_target_msg that can
% delete targets from the list of command line targets, in
% make.module_target.m and make.program_target.m respectively,
% both do so only if the target is NOT up to date, and
%
% - it should not require this make_info field to prevent duplicate
% calls to this predicate if the target IS up to date.
%
:- pred maybe_warn_up_to_date_target_msg(globals::in, top_target_file::in,
file_name::in, make_info::in, make_info::out, string::out) is det.
% Write a message "Made symlink/copy of <filename>"
% if `--verbose-make' is set.
%
:- pred maybe_symlink_or_copy_linked_target_msg(globals::in, file_name::in,
string::out) is det.
%---------------------%
%
% All messages from the make-like functionality of "mmc --make" should be
% written out using a single call to io.write_string, and then a flush
% of the stream. This should allow messages from different processes
% during parallel builds to be interleaved *only* on message boundaries.
%
% XXX This is only really true for the "locked" variants, and even then,
% see the other XXX just below.
%
% Write out a message from mmc --make, if the message is not empty.
%
:- pred maybe_write_msg(io.text_output_stream::in, string::in,
io::di, io::uo) is det.
% Write out a message from mmc --make, if the message is not empty,
% while locking stdout.
%
% XXX The current stream, or the specified stream, may be a stream
% OTHER THAN stdout.
%
:- pred maybe_write_msg_locked(io.text_output_stream::in, make_info::in,
string::in, io::di, io::uo) is det.
%---------------------------------------------------------------------------%
%
% Timing.
%
:- pred get_real_milliseconds(int::out, io::di, io::uo) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module backend_libs.
:- import_module backend_libs.compile_target_code.
:- import_module make.build.
:- import_module make.file_names.
:- import_module make.timestamp.
:- import_module bool.
:- import_module io.file.
:- import_module map.
:- import_module maybe.
:- import_module set.
:- import_module string.
:- import_module version_hash_table.
%---------------------------------------------------------------------------%
remove_make_target_file(ProgressStream, Globals, From, VerboseOption, Target,
!Info, !IO) :-
Target = target_file(ModuleName, TargetType),
remove_make_target_file_by_name(ProgressStream, Globals, From,
VerboseOption, ModuleName, TargetType, !Info, !IO).
%---------------------%
remove_make_target_file_by_name(ProgressStream, Globals, From, VerboseOption,
ModuleName, TargetType, !Info, !IO) :-
% XXX LEGACY
module_target_to_file_name(Globals, From, TargetType,
ModuleName, FileNameLegacy, FileNameProposed, !IO),
remove_file_for_make(ProgressStream, Globals, VerboseOption,
FileNameLegacy, !Info, !IO),
remove_file_for_make(ProgressStream, Globals, VerboseOption,
FileNameProposed, !Info, !IO),
( if date_file_extension(TargetType, TimestampExt) then
remove_module_file_for_make(ProgressStream, Globals, VerboseOption,
ModuleName, TimestampExt, !Info, !IO)
else
true
).
%---------------------%
remove_module_file_for_make(ProgressStream, Globals, VerboseOption,
ModuleName, Ext, !Info, !IO) :-
% XXX LEGACY
module_name_to_file_name(Globals, $pred, Ext, ModuleName,
FileNameLegacy, FileNameProposed),
remove_file_for_make(ProgressStream, Globals, VerboseOption,
FileNameLegacy, !Info, !IO),
remove_file_for_make(ProgressStream, Globals, VerboseOption,
FileNameProposed, !Info, !IO).
%---------------------%
remove_file_for_make(ProgressStream, Globals, VerboseOption, FileName,
!Info, !IO) :-
option_set_two_part_msg(Globals, VerboseOption,
"Removing", FileName, RemovingMsg),
maybe_write_msg(ProgressStream, RemovingMsg, !IO),
io.file.remove_file(FileName, _, !IO),
FileTimestampMap0 = make_info_get_file_timestamp_map(!.Info),
map.delete(FileName, FileTimestampMap0, FileTimestampMap),
make_info_set_file_timestamp_map(FileTimestampMap, !Info),
% For simplicity, clear out all target file timestamps.
make_info_set_target_file_timestamp_map(init_target_file_timestamp_map,
!Info).
%---------------------------------------------------------------------------%
make_target_file_list(ModuleNames, TargetType) =
list.map((func(ModuleName) = target_file(ModuleName, TargetType)),
ModuleNames).
make_target_id_list(ModuleNames, TargetType) =
list.map((func(Module) = merc_target(target_file(Module, TargetType))),
ModuleNames).
%---------------------------------------------------------------------------%
is_target_grade_dependent(Target) = IsDependent :-
(
( Target = module_target_source
; Target = module_target_errors
; Target = module_target_int0
; Target = module_target_int1
; Target = module_target_int2
; Target = module_target_int3
; Target = module_target_c_header(header_mh)
; Target = module_target_xml_doc
),
IsDependent = not_grade_dependent
;
( Target = module_target_opt
; Target = module_target_analysis_registry
; Target = module_target_track_flags
; Target = module_target_c_header(header_mih)
; Target = module_target_c_code
; Target = module_target_csharp_code
; Target = module_target_java_code
; Target = module_target_java_class_code
; Target = module_target_object_code(_)
; Target = module_target_fact_table_object(_, _)
),
IsDependent = grade_dependent
).
%---------------------------------------------------------------------------%
should_we_stop_or_continue(KeepGoing, JobSucceeded, StopOrContinue,
!Succeeded) :-
(
JobSucceeded = succeeded,
StopOrContinue = soc_continue
;
JobSucceeded = did_not_succeed,
!:Succeeded = did_not_succeed,
(
KeepGoing = do_not_keep_going,
StopOrContinue = soc_stop
;
KeepGoing = do_keep_going,
StopOrContinue = soc_continue
)
).
%---------------------------------------------------------------------------%
%
% Debugging, progress, and error messages.
%
debug_make_msg(Globals, MakeMsgPred, Msg) :-
globals.lookup_bool_option(Globals, debug_make, DebugMake),
(
DebugMake = no,
Msg = ""
;
DebugMake = yes,
MakeMsgPred(Msg)
).
%---------------------%
verbose_make_one_part_msg(Globals, Part1, Msg) :-
option_set_one_part_msg(Globals, verbose_make, Part1, Msg).
verbose_make_two_part_msg(Globals, Part1, Part2, Msg) :-
option_set_two_part_msg(Globals, verbose_make, Part1, Part2, Msg).
verbose_make_three_part_msg(Globals, Part1, Part2, Part3, Msg) :-
option_set_three_part_msg(Globals, verbose_make, Part1, Part2, Part3, Msg).
verbose_make_four_part_msg(Globals, Part1, Part2, Part3, Part4, Msg) :-
option_set_four_part_msg(Globals, verbose_make, Part1, Part2, Part3,
Part4, Msg).
option_set_one_part_msg(Globals, Option, Part1, Msg) :-
globals.lookup_bool_option(Globals, Option, OptionValue),
(
OptionValue = no,
Msg = ""
;
OptionValue = yes,
string.format("%s\n", [s(Part1)], Msg)
).
option_set_two_part_msg(Globals, Option, Part1, Part2, Msg) :-
globals.lookup_bool_option(Globals, Option, OptionValue),
(
OptionValue = no,
Msg = ""
;
OptionValue = yes,
string.format("%s %s\n", [s(Part1), s(Part2)], Msg)
).
option_set_three_part_msg(Globals, Option, Part1, Part2, Part3, Msg) :-
globals.lookup_bool_option(Globals, Option, OptionValue),
(
OptionValue = no,
Msg = ""
;
OptionValue = yes,
string.format("%s %s %s\n", [s(Part1), s(Part2), s(Part3)], Msg)
).
option_set_four_part_msg(Globals, Option, Part1, Part2, Part3, Part4, Msg) :-
globals.lookup_bool_option(Globals, Option, OptionValue),
(
OptionValue = no,
Msg = ""
;
OptionValue = yes,
string.format("%s %s %s %s\n",
[s(Part1), s(Part2), s(Part3), s(Part4)], Msg)
).
%---------------------%
maybe_making_filename_msg(Globals, FileName, Msg) :-
globals.lookup_bool_option(Globals, verbose_make, VerboseMake),
(
VerboseMake = no,
Msg = ""
;
VerboseMake = yes,
string.format("Making %s\n", [s(FileName)], Msg)
).
maybe_reanalyse_modules_msg(Globals, Msg) :-
globals.lookup_bool_option(Globals, verbose_make, VerboseMake),
(
VerboseMake = no,
Msg = ""
;
VerboseMake = yes,
Msg = "Reanalysing invalid/suboptimal modules\n"
).
file_error_msg(FileName, Msg) :-
string.format("** Error making `%s'.\n", [s(FileName)], Msg).
% with_locked_stdout(Info, io.write_string(Msg), !IO).
maybe_warn_up_to_date_target_msg(Globals, Target, FileName, !Info, Msg) :-
globals.lookup_bool_option(Globals, warn_up_to_date, Warn),
CmdLineTargets0 = make_info_get_command_line_targets(!.Info),
(
Warn = yes,
( if set.member(Target, CmdLineTargets0) then
string.format("** Nothing to be done for `%s'.\n", [s(FileName)],
Msg)
else
Msg = ""
)
;
Warn = no,
Msg = ""
),
set.delete(Target, CmdLineTargets0, CmdLineTargets),
make_info_set_command_line_targets(CmdLineTargets, !Info).
maybe_symlink_or_copy_linked_target_msg(Globals, FileName, Msg) :-
globals.lookup_bool_option(Globals, verbose_make, VerboseMake),
(
VerboseMake = no,
Msg = ""
;
VerboseMake = yes,
string.format("Made symlink/copy of %s\n", [s(FileName)], Msg)
).
%---------------------%
maybe_write_msg(OutStream, Msg, !IO) :-
( if Msg = "" then
true
else
io.write_string(OutStream, Msg, !IO),
io.flush_output(OutStream, !IO)
).
maybe_write_msg_locked(OutStream, Info, Msg, !IO) :-
( if Msg = "" then
true
else
MaybeLock = make_info_get_maybe_stdout_lock(Info),
(
MaybeLock = yes(Lock),
lock_stdout(Lock, !IO),
io.write_string(OutStream, Msg, !IO),
io.flush_output(OutStream, !IO),
unlock_stdout(Lock, !IO)
;
MaybeLock = no,
io.write_string(OutStream, Msg, !IO),
io.flush_output(OutStream, !IO)
)
).
%---------------------------------------------------------------------------%
%
% Timing.
%
:- pragma foreign_proc("C",
get_real_milliseconds(Time::out, _IO0::di, _IO::uo),
[will_not_call_mercury, promise_pure, thread_safe, tabled_for_io],
"
Time = MR_get_real_milliseconds();
").
:- pragma foreign_proc("C#",
get_real_milliseconds(Time::out, _IO0::di, _IO::uo),
[will_not_call_mercury, promise_pure, thread_safe, tabled_for_io],
"
Time = System.Environment.TickCount;
").
:- pragma foreign_proc("Java",
get_real_milliseconds(Time::out, _IO0::di, _IO::uo),
[will_not_call_mercury, promise_pure, thread_safe, tabled_for_io],
"
// The loss of precision is acceptable for mmc --make.
Time = (int) System.currentTimeMillis();
").
get_real_milliseconds(_, _, _) :-
sorry($file, $pred).
%---------------------------------------------------------------------------%
:- end_module make.util.
%---------------------------------------------------------------------------%