Files
mercury/compiler/make.track_flags.m
Zoltan Somogyi cdb42065e4 Give some getopt predicates more descriptive names.
library/getopt_io.m:
    Rename record_all_arguments as recognize_all_options, which better
    describes what it does.

    Keep the old record_arguments predicate arounds (with its original
    argument order), but mark it obsolete, and define recognize_options
    as its replacement.

library/getopt.m:
    Automatic copy of getopt_io.m.

NEWS.md:
    Announce the above changes.

compiler/mercury_compile_args.m:
    Report *all* errors we find in args files.

compiler/make.track_flags.m:
    Conform to the changes above.
2025-12-18 15:19:05 +11:00

356 lines
15 KiB
Mathematica

%---------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%---------------------------------------------------------------------------%
% Copyright (C) 2002-2012 The University of Melbourne.
% Copyright (C) 2013-2021, 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.track_flags.m.
%
% This module keeps track of which options were used to compile which modules.
%
%---------------------------------------------------------------------------%
:- module make.track_flags.
:- interface.
:- import_module libs.
:- import_module libs.globals.
:- import_module libs.maybe_util.
:- import_module make.make_info.
:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module io.
% Generate the .track_flags files for local modules reachable from the
% target module. The files contain hashes of the options which are set for
% that particular module (deliberately ignoring some options), and are only
% updated if they have changed since the last --make run. We use hashes
% as the full option tables are quite large.
%
:- pred make_track_flags_files(io.text_output_stream::in,
io.text_output_stream::in, globals::in,
module_name::in, maybe_succeeded::out,
make_info::in, make_info::out, io::di, io::uo) is det.
%---------------------------------------------------------------------------%
%---------------------------------------------------------------------------%
:- implementation.
:- import_module libs.handle_options.
:- import_module libs.md5.
:- import_module libs.options.
:- import_module make.find_local_modules.
:- import_module make.options_file.
:- import_module parse_tree.
:- import_module parse_tree.error_spec.
:- import_module parse_tree.file_names.
:- import_module parse_tree.maybe_error.
:- import_module parse_tree.write_error_spec.
:- import_module bool.
:- import_module getopt.
:- import_module list.
:- import_module map.
:- import_module maybe.
:- import_module pair.
:- import_module require.
:- import_module set.
:- import_module set_tree234.
:- import_module string.
%---------------------------------------------------------------------------%
:- type last_hash
---> last_hash(
lh_maybe_stdlib_grades :: maybe_stdlib_grades,
lh_maybe_stdlib_dirs :: maybe1(list(string)),
lh_options :: list(string),
lh_hash :: string
).
make_track_flags_files(ErrorStream, ProgressStream, Globals, ModuleName,
Succeeded, !Info, !IO) :-
find_reachable_local_modules(ProgressStream, Globals, ModuleName,
Succeeded0, ModuleNames, !Info, !IO),
(
Succeeded0 = succeeded,
DummyLastHash = last_hash(stdlib_grades_unknown, error1([]), [], ""),
foldl3_make_track_flags_for_modules_loop(ErrorStream, ProgressStream,
Globals, set.to_sorted_list(ModuleNames),
Succeeded, DummyLastHash, _LastHash, !Info, !IO)
;
Succeeded0 = did_not_succeed,
Succeeded = did_not_succeed
).
%---------------------------------------------------------------------------%
:- pred foldl3_make_track_flags_for_modules_loop(io.text_output_stream::in,
io.text_output_stream::in, globals::in,
list(module_name)::in, maybe_succeeded::out, last_hash::in, last_hash::out,
make_info::in, make_info::out, io::di, io::uo) is det.
foldl3_make_track_flags_for_modules_loop(_ErrorStream, _ProgressStream,
_Globals, [], succeeded, !LastHash, !Info, !IO).
foldl3_make_track_flags_for_modules_loop(ErrorStream, ProgressStream,
Globals, [ModuleName | ModuleNames], Succeeded,
!LastHash, !Info, !IO) :-
make_track_flags_files_for_module(ErrorStream, ProgressStream,
Globals, !.Info, ModuleName, Succeeded0, !LastHash, !IO),
(
Succeeded0 = succeeded,
foldl3_make_track_flags_for_modules_loop(ErrorStream, ProgressStream,
Globals, ModuleNames, Succeeded, !LastHash, !Info, !IO)
;
Succeeded0 = did_not_succeed,
Succeeded = did_not_succeed
).
%---------------------------------------------------------------------------%
:- pred make_track_flags_files_for_module(io.text_output_stream::in,
io.text_output_stream::in, globals::in, make_info::in, module_name::in,
maybe_succeeded::out, last_hash::in, last_hash::out,
io::di, io::uo) is det.
make_track_flags_files_for_module(ErrorStream, ProgressStream, Globals, Info,
ModuleName, Succeeded, !LastHash, !IO) :-
Params = make_info_get_compiler_params(Info),
Params = compiler_params(EnvOptFileVariables, EnvVarArgs, OptionArgs),
lookup_mmc_module_options(EnvOptFileVariables, ModuleName,
MaybeModuleOptionArgs),
(
MaybeModuleOptionArgs = ok1(ModuleOptionArgs),
MaybeStdLibGrades = make_info_get_maybe_stdlib_grades(Info),
lookup_mercury_stdlib_dir(EnvOptFileVariables, MaybeStdLibDirs),
AllOptionArgs = ModuleOptionArgs ++ EnvVarArgs ++ OptionArgs,
% The set of options from one module to the next is usually identical,
% so we can easily avoid running handle_options and stringifying and
% hashing the option table, all of which can contribute to an annoying
% delay when mmc --make starts.
( if
!.LastHash = last_hash(MaybeStdLibGrades, MaybeStdLibDirs,
AllOptionArgs, HashPrime)
then
Hash = HashPrime
else
get_default_options(Globals, DefaultOptionTable),
option_table_hash(ProgressStream, DefaultOptionTable,
MaybeStdLibGrades, MaybeStdLibDirs, AllOptionArgs, Hash, !IO),
!:LastHash = last_hash(MaybeStdLibGrades, MaybeStdLibDirs,
AllOptionArgs, Hash)
),
% XXX LEGACY
module_name_to_file_name_create_dirs(Globals, $pred,
ext_cur_ngs_gs(ext_cur_ngs_gs_misc_track_flags),
ModuleName, HashFileName, _HashFileNameProposed, !IO),
compare_hash_file(ProgressStream, Globals, HashFileName,
Hash, Same, !IO),
(
Same = yes,
Succeeded = succeeded
;
Same = no,
write_hash_file(ProgressStream, HashFileName, Hash, Succeeded, !IO)
)
;
MaybeModuleOptionArgs = error1(LookupSpecs),
% XXX ErrorStream is the error stream for the top target,
% which may or may NOT be ModuleName. It is not clear to me (zs)
% whether the right place to write LookupSpecs is ErrorStream,
% or a stream wrting to the .err file of ModuleName.
% I also don't think we have a good basis for deciding the answer
% to that question until we have practical experience with
% compiler invocations that lead execution to this spot.
write_error_specs(ErrorStream, Globals, LookupSpecs, !IO),
Succeeded = did_not_succeed
).
%---------------------------------------------------------------------------%
:- pred option_table_hash(io.text_output_stream::in, option_table::in,
maybe_stdlib_grades::in, maybe1(list(string))::in, list(string)::in,
string::out, io::di, io::uo) is det.
option_table_hash(ProgressStream, DefaultOptionTable, MaybeStdLibGrades,
MaybeStdLibDirs, AllOptionArgs, Hash, !IO) :-
% This code is part of the --track-flags implementation. We hash the
% options in the updated globals because they include module-specific
% options. The hash is then compared with the hash stored in the
% <module_name>.track_flags file, which is updated if it differs.
% The new timestamp will later force the module to be recompiled
% if necessary, but that's later. We are not compiling the module
% immediately, so this is the only use we have for AllOptionArgsGlobals
% here.
%
% XXX This algorithm processes every option in the option table, even
% though it does not include all of them in the hash. Virtually all
% of these options will have their default values, which makes
% most of that work effectively wasted.
%
% A more elegant approach would be to invoke getopt.recognize_all_options
% on AllOptionArgs, and hash the resulting list of option_values.
% This would require hashing special options (such as -ON) as well as
% non-special values, but the cost of that would be trivial.
%
% This approach would have two principal differences from the current one.
%
% - First, the current approach computes a different hash if AllOptionArgs
% has not changed, but the default value of an (consequential) option
% has changed, or if a new consequential option has been added.
% The recompilation that this forces will be needed after some changes
% to the option defaults, but not after others.
%
% However, this consideration never applies only to a single module;
% if the default set of option values changes, it applies for all
% modules. Therefore it would be enough to record a hash of the
% default values of all options (or a timestamp when the option database
% was last modified, see below) *once* in a global file that is relevant
% to all modules in a directory, named maybe "Mercury.track_flags",
% which, if it changes, invalidates *every* <module_name>.track_flags
% file in that directory.
%
% - Second, it is possible for some changes in AllOptionArgs to yield
% the same final AllOptionArgsGlobals, if some option in the old
% AllOptionArgs implies the value of some other option, and the new
% AllOptionArgs explicitly sets this option to the implied value
% (or vice versa). In such cases, the new approach would force a
% recompilation, while the old one would not. However, the absence
% of a recompilation in such an instance could be more worrysome
% than welcome for users who do not know about that option implication,
% or who do not appreciate its significance.
%
% Note also that while the old approach forces a recompilation
% both when the set of options changes and when the default value
% of an option changes, it does *not* force a recompilation
% if the *meaning* of an option is redefined. We could consider
% all three of these kinds of changes grounds for updating a timestamp
% of when the option database last changed.
%
% XXX A somewhat more backwards-compatible version of the above approach
% would be to hash both DefaultOptionTable AND the result invoking
% getopt.recognize_all_options, after filtering inconsequential options
% out of both. This would effectively hash the *input* of the call to
% handle_given_options, rather than its output. It would also be
% logically cleaner, since in some cases, handle_given_options does
% updates on mutables (e.g. for disabling smart recompilation)
% whose effects persist *beyond* the processing of a given module.
%
handle_given_options(ProgressStream, DefaultOptionTable, MaybeStdLibGrades,
MaybeStdLibDirs, AllOptionArgs, _, _,
OptionsErrors, AllOptionArgsGlobals, !IO),
(
OptionsErrors = []
;
OptionsErrors = [_ | _],
unexpected($file, $pred ++ ": " ++
"handle_options returned with errors")
),
globals.get_options(AllOptionArgsGlobals, OptionTable),
map.to_sorted_assoc_list(OptionTable, OptionList),
DoNotTrackOptions = set_tree234.list_to_set(options_not_to_track),
list.filter(include_option_in_hash(DoNotTrackOptions),
OptionList, HashOptionList),
globals.get_opt_tuple(AllOptionArgsGlobals, OptTuple),
Hash = md5sum(string({HashOptionList, OptTuple})).
:- pred include_option_in_hash(set_tree234(option)::in,
pair(option, option_data)::in) is semidet.
include_option_in_hash(InconsequentialOptionsSet, Option - OptionData) :-
require_complete_switch [OptionData]
(
( OptionData = bool(_)
; OptionData = int(_)
; OptionData = string(_)
; OptionData = maybe_int(_)
; OptionData = maybe_string(_)
; OptionData = accumulating(_)
),
% XXX Reconsider if a lot of these options really should be ignored.
not set_tree234.contains(InconsequentialOptionsSet, Option)
;
( OptionData = special
; OptionData = bool_special
; OptionData = int_special
; OptionData = string_special
; OptionData = maybe_string_special
; OptionData = file_special
),
% There is no point hashing special options as the option_data is
% always the same.
fail
).
:- pred compare_hash_file(io.text_output_stream::in, globals::in,
string::in, string::in, bool::out, io::di, io::uo) is det.
compare_hash_file(ProgressStream, Globals, FileName, Hash, Same, !IO) :-
io.open_input(FileName, OpenResult, !IO),
(
OpenResult = ok(Stream),
io.read_line_as_string(Stream, ReadResult, !IO),
(
ReadResult = ok(Line),
( if Line = Hash then
Same = yes
else
Same = no
)
;
ReadResult = eof,
Same = no
;
ReadResult = error(_),
Same = no
),
io.close_input(Stream, !IO)
;
OpenResult = error(_),
% Probably missing file.
Same = no
),
globals.lookup_bool_option(Globals, verbose, Verbose),
(
Verbose = yes,
(
Same = yes,
io.format(ProgressStream, "%% %s does not need updating.\n",
[s(FileName)], !IO)
;
Same = no,
io.format(ProgressStream, "%% %s will be UPDATED.\n",
[s(FileName)], !IO)
)
;
Verbose = no
).
:- pred write_hash_file(io.text_output_stream::in, string::in, string::in,
maybe_succeeded::out, io::di, io::uo) is det.
write_hash_file(ProgressStream, FileName, Hash, Succeeded, !IO) :-
io.open_output(FileName, OpenResult, !IO),
(
OpenResult = ok(Stream),
io.write_string(Stream, Hash, !IO),
io.close_output(Stream, !IO),
Succeeded = succeeded
;
OpenResult = error(Error),
io.format(ProgressStream, "Error creating `%s': %s\n",
[s(FileName), s(io.error_message(Error))], !IO),
Succeeded = did_not_succeed
).
%---------------------------------------------------------------------------%
:- end_module make.track_flags.
%---------------------------------------------------------------------------%