mirror of
https://github.com/Mercury-Language/mercury.git
synced 2025-12-10 11:23:15 +00:00
Branches: main Prevent interleaved error message output when using parallel `mmc --make'. compiler/make.m: Add a field to `make_info' to hold an inter-process lock. compiler/make.util.m: Reuse the `job_ctl' type as the stdout lock. Add predicates to acquire and release the lock. Make `foldl2_maybe_stop_at_error_parallel_processes' set the stdout lock in `make_info' for child processes to see. Acquire and release the stdout lock in various predicates that write errors messages. compiler/make.module_target.m: compiler/make.program_target.m: Conform to changes.
756 lines
26 KiB
Mathematica
756 lines
26 KiB
Mathematica
%-----------------------------------------------------------------------------%
|
|
% vim: ft=mercury ts=4 sw=4 et
|
|
%-----------------------------------------------------------------------------%
|
|
% Copyright (C) 2002-2012 The 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: make.m.
|
|
% Main author: stayl.
|
|
%
|
|
% A builtin Mercury-specific make replacement.
|
|
%
|
|
% TODO:
|
|
% - distributed builds
|
|
%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- module make.
|
|
:- interface.
|
|
|
|
:- include_module make.options_file.
|
|
:- include_module make.util.
|
|
|
|
:- import_module libs.file_util.
|
|
:- import_module libs.globals.
|
|
:- import_module make.options_file.
|
|
:- import_module mdbcomp.
|
|
:- import_module mdbcomp.prim_data.
|
|
:- import_module parse_tree.
|
|
:- import_module parse_tree.module_imports.
|
|
|
|
:- import_module io.
|
|
:- import_module list.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
% make.process_args(OptionArgs, NonOptionArgs).
|
|
%
|
|
:- pred make_process_args(globals::in, options_variables::in, list(string)::in,
|
|
list(file_name)::in, io::di, io::uo) is det.
|
|
|
|
:- pred make_write_module_dep_file(globals::in, module_and_imports::in,
|
|
io::di, io::uo) is det.
|
|
|
|
:- func make_module_dep_file_extension = string.
|
|
|
|
:- type make_info.
|
|
|
|
:- type rebuild_module_deps
|
|
---> do_rebuild_module_deps
|
|
; do_not_rebuild_module_deps.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- include_module make.dependencies.
|
|
:- include_module make.module_dep_file.
|
|
:- include_module make.module_target.
|
|
:- include_module make.program_target.
|
|
|
|
:- import_module backend_libs.
|
|
:- import_module backend_libs.compile_target_code.
|
|
:- import_module hlds.
|
|
:- import_module libs.
|
|
:- import_module libs.handle_options.
|
|
:- import_module libs.md4.
|
|
:- import_module libs.options.
|
|
:- import_module libs.timestamp.
|
|
:- import_module make.dependencies.
|
|
:- import_module make.module_dep_file.
|
|
:- import_module make.module_target.
|
|
:- import_module make.program_target.
|
|
:- import_module make.util.
|
|
:- import_module parse_tree.file_names.
|
|
:- import_module parse_tree.error_util.
|
|
:- import_module top_level. % XXX unwanted dependency
|
|
:- import_module top_level.mercury_compile. % XXX unwanted dependency
|
|
|
|
:- import_module assoc_list.
|
|
:- import_module bool.
|
|
:- import_module dir.
|
|
:- import_module int.
|
|
:- import_module getopt_io.
|
|
:- import_module map.
|
|
:- import_module maybe.
|
|
:- import_module pair.
|
|
:- import_module require.
|
|
:- import_module set.
|
|
:- import_module solutions.
|
|
:- import_module string.
|
|
:- import_module sparse_bitset.
|
|
:- import_module version_array.
|
|
:- import_module version_hash_table.
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type make_info
|
|
---> make_info(
|
|
% The items field of each module_and_imports structure
|
|
% should be empty -- we're not trying to cache the items here.
|
|
module_dependencies :: map(module_name,
|
|
maybe(module_and_imports)),
|
|
|
|
file_timestamps :: file_timestamps,
|
|
|
|
% Cache chosen file names for a module name and extension.
|
|
search_file_name_cache :: map(pair(module_name, string),
|
|
file_name),
|
|
|
|
% The original set of options passed to mmc, not including
|
|
% the targets to be made.
|
|
option_args :: list(string),
|
|
|
|
% The contents of the Mercury.options file.
|
|
options_variables :: options_variables,
|
|
|
|
% The mapping between module_names and indices.
|
|
module_index_map :: module_index_map,
|
|
|
|
% The mapping between dependency_files and indices.
|
|
dep_file_index_map :: dependency_file_index_map,
|
|
|
|
dependency_status :: version_hash_table(dependency_file,
|
|
dependency_status),
|
|
|
|
% For each module, the set of modules for which the `.int'
|
|
% files are read, excluding those read as a result of reading
|
|
% `.opt' files. The bool records whether there was an error
|
|
% in the dependencies.
|
|
% XXX Use a better representation for the sets.
|
|
cached_direct_imports :: cached_direct_imports,
|
|
|
|
cached_non_intermod_direct_imports
|
|
:: cached_direct_imports,
|
|
|
|
% The boolean is `yes' if the result is complete.
|
|
% XXX Use a better representation for the sets.
|
|
cached_transitive_dependencies
|
|
:: cached_transitive_dependencies,
|
|
|
|
cached_foreign_imports :: cached_foreign_imports,
|
|
|
|
% Should the `.module_dep' files be rebuilt.
|
|
% Set to `no' for `mmc --make clean'.
|
|
rebuild_module_deps :: rebuild_module_deps,
|
|
|
|
keep_going :: bool,
|
|
|
|
% Modules for which we have redirected output
|
|
% to a `.err' file during this invocation of mmc.
|
|
error_file_modules :: set(module_name),
|
|
|
|
% Used for reporting which module imported a nonexistent
|
|
% module.
|
|
importing_module :: maybe(module_name),
|
|
|
|
% Targets specified on the command line.
|
|
command_line_targets :: set(pair(module_name, target_type)),
|
|
|
|
% The remaining number of analysis passes that we will allow on
|
|
% `suboptimal' modules. It starts at the value of
|
|
% `--analysis-repeat' and decrements to zero as analysis passes
|
|
% on `suboptimal' modules are performed. `invalid' modules
|
|
% are not affected as they will always be reanalysed.
|
|
reanalysis_passes :: int,
|
|
|
|
% An inter-process lock to prevent multiple processes
|
|
% interleaving their output to standard output.
|
|
maybe_stdout_lock :: maybe(stdout_lock)
|
|
).
|
|
|
|
:- type module_index_map
|
|
---> module_index_map(
|
|
mim_forward_map :: version_hash_table(module_name,
|
|
module_index),
|
|
mim_reverse_map :: version_array(module_name),
|
|
mim_counter :: int
|
|
).
|
|
|
|
:- type dependency_file_index_map
|
|
---> dependency_file_index_map(
|
|
dfim_forward_map :: version_hash_table(dependency_file,
|
|
dependency_file_index),
|
|
dfim_reverse_map :: version_array(dependency_file),
|
|
dfim_counter :: int
|
|
).
|
|
|
|
:- type make_error
|
|
---> make_error_target(target_file)
|
|
; make_error_dependencies(module_name)
|
|
; make_error_other(string).
|
|
|
|
:- type compilation_task_type
|
|
---> process_module(module_compilation_task_type)
|
|
|
|
% The `pic' argument is only used for
|
|
% `--target c' and `--target asm'.
|
|
; target_code_to_object_code(pic)
|
|
; foreign_code_to_object_code(pic, foreign_language)
|
|
; fact_table_code_to_object_code(pic, file_name).
|
|
|
|
:- type module_compilation_task_type
|
|
---> task_errorcheck
|
|
; task_make_short_interface
|
|
; task_make_interface
|
|
; task_make_private_interface
|
|
; task_make_optimization_interface
|
|
; task_make_analysis_registry
|
|
; task_compile_to_target_code
|
|
; task_make_xml_doc.
|
|
|
|
:- type module_target_type
|
|
---> module_target_source
|
|
; module_target_errors
|
|
; module_target_private_interface
|
|
; module_target_long_interface
|
|
; module_target_short_interface
|
|
; module_target_unqualified_short_interface
|
|
; module_target_intermodule_interface
|
|
; module_target_analysis_registry
|
|
; module_target_track_flags
|
|
; module_target_c_header(c_header_type)
|
|
; module_target_c_code
|
|
; module_target_il_code
|
|
; module_target_il_asm
|
|
; module_target_csharp_code
|
|
; module_target_java_code
|
|
; module_target_java_class_code
|
|
; module_target_erlang_header
|
|
; module_target_erlang_code
|
|
; module_target_erlang_beam_code
|
|
; module_target_asm_code(pic)
|
|
; module_target_object_code(pic)
|
|
; module_target_foreign_il_asm(foreign_language)
|
|
; module_target_foreign_object(pic, foreign_language)
|
|
; module_target_fact_table_object(pic, file_name)
|
|
; module_target_xml_doc.
|
|
|
|
:- type c_header_type
|
|
---> header_mh % For `:- pragma foreign_export' declarations.
|
|
; header_mih. % Declarations for hlc grades, for compiler use only.
|
|
|
|
% :- type linked_target_type in compile_target_code.m.
|
|
|
|
:- type misc_target_type
|
|
---> misc_target_clean
|
|
; misc_target_realclean
|
|
; misc_target_build_all(module_target_type)
|
|
; misc_target_build_analyses
|
|
; misc_target_build_library
|
|
; misc_target_install_library
|
|
; misc_target_build_xml_docs.
|
|
|
|
:- type file_timestamps == map(string, maybe_error(timestamp)).
|
|
|
|
:- type dependency_status
|
|
---> deps_status_not_considered
|
|
; deps_status_being_built
|
|
; deps_status_up_to_date
|
|
; deps_status_error.
|
|
|
|
:- type target_file
|
|
---> target_file(
|
|
target_file_name :: module_name,
|
|
target_file_type :: module_target_type
|
|
).
|
|
|
|
:- type linked_target_file
|
|
---> linked_target_file(
|
|
linked_tf_name :: module_name,
|
|
linked_tf_type :: linked_target_type
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
make_write_module_dep_file(Globals, Imports, !IO) :-
|
|
make.module_dep_file.write_module_dep_file(Globals, Imports, !IO).
|
|
|
|
make_module_dep_file_extension = ".module_dep".
|
|
|
|
make_process_args(Globals, Variables, OptionArgs, Targets0, !IO) :-
|
|
(
|
|
Targets0 = [],
|
|
lookup_main_target(Globals, Variables, MaybeMAIN_TARGET, !IO),
|
|
(
|
|
MaybeMAIN_TARGET = yes(Targets),
|
|
(
|
|
Targets = [_ | _],
|
|
Continue0 = yes
|
|
;
|
|
Targets = [],
|
|
Continue0 = no,
|
|
io.write_string("** Error: no targets specified " ++
|
|
"and `MAIN_TARGET' not defined.\n", !IO)
|
|
)
|
|
;
|
|
MaybeMAIN_TARGET = no,
|
|
Targets = [],
|
|
Continue0 = no
|
|
)
|
|
;
|
|
Targets0 = [_ | _],
|
|
Continue0 = yes,
|
|
Targets = Targets0
|
|
),
|
|
|
|
% Ensure none of the targets contains the directory_separator. Such targets
|
|
% are not supported by the rest of the code.
|
|
|
|
list.filter(
|
|
(pred(Target::in) is semidet :-
|
|
string.contains_char(Target, dir.directory_separator)
|
|
), Targets, AbsTargets),
|
|
(
|
|
AbsTargets = [],
|
|
Continue = Continue0
|
|
;
|
|
AbsTargets = [_ | _],
|
|
Continue = no,
|
|
list.foldl(
|
|
(pred(Target::in, !.I::di, !:I::uo) is det :-
|
|
error_util.write_error_plain_with_progname(Target,
|
|
"Make target must not contain any directory component.", !I)
|
|
), AbsTargets, !IO)
|
|
),
|
|
(
|
|
Continue = no,
|
|
io.set_exit_status(1, !IO)
|
|
;
|
|
Continue = yes,
|
|
globals.lookup_bool_option(Globals, keep_going, KeepGoing),
|
|
|
|
ModuleIndexMap = module_index_map(
|
|
version_hash_table.init_default(module_name_hash),
|
|
version_array.empty, 0),
|
|
DepIndexMap = dependency_file_index_map(
|
|
version_hash_table.init_default(dependency_file_hash),
|
|
version_array.empty, 0),
|
|
DepStatusMap = version_hash_table.init_default(dependency_file_hash),
|
|
|
|
% Accept and ignore `.depend' targets. `mmc --make' does not need
|
|
% a separate make depend step. The dependencies for each module
|
|
% are regenerated on demand.
|
|
NonDependTargets = list.filter(
|
|
(pred(Target::in) is semidet :-
|
|
\+ string.suffix(Target, ".depend")
|
|
), Targets),
|
|
|
|
% Classify the remaining targets.
|
|
list.map(classify_target(Globals), NonDependTargets,
|
|
ClassifiedTargets),
|
|
|
|
ShouldRebuildModuleDeps = do_rebuild_module_deps,
|
|
globals.lookup_int_option(Globals, analysis_repeat, AnalysisRepeat),
|
|
|
|
MakeInfo0 = make_info(map.init, map.init, map.init,
|
|
OptionArgs, Variables,
|
|
ModuleIndexMap,
|
|
DepIndexMap,
|
|
DepStatusMap,
|
|
init_cached_direct_imports,
|
|
init_cached_direct_imports,
|
|
init_cached_transitive_dependencies,
|
|
init_cached_foreign_imports,
|
|
ShouldRebuildModuleDeps, KeepGoing,
|
|
set.init, no, set.list_to_set(ClassifiedTargets),
|
|
AnalysisRepeat, no),
|
|
|
|
% Build the targets, stopping on any errors if `--keep-going'
|
|
% was not set.
|
|
|
|
foldl2_maybe_stop_at_error(KeepGoing, make_target, Globals,
|
|
ClassifiedTargets, Success, MakeInfo0, _MakeInfo, !IO),
|
|
|
|
(
|
|
Success = no,
|
|
io.set_exit_status(1, !IO)
|
|
;
|
|
Success = yes
|
|
)
|
|
).
|
|
|
|
:- pred make_target(globals::in, pair(module_name, target_type)::in, bool::out,
|
|
make_info::in, make_info::out, io::di, io::uo) is det.
|
|
|
|
make_target(Globals, Target, Success, !Info, !IO) :-
|
|
Target = ModuleName - TargetType,
|
|
globals.lookup_bool_option(Globals, track_flags, TrackFlags),
|
|
(
|
|
TrackFlags = no,
|
|
TrackFlagsSuccess = yes
|
|
;
|
|
TrackFlags = yes,
|
|
make_track_flags_files(Globals, ModuleName, TrackFlagsSuccess, !Info,
|
|
!IO)
|
|
),
|
|
(
|
|
TrackFlagsSuccess = yes,
|
|
(
|
|
TargetType = module_target(ModuleTargetType),
|
|
TargetFile = target_file(ModuleName, ModuleTargetType),
|
|
make_module_target(Globals, dep_target(TargetFile), Success,
|
|
!Info, !IO)
|
|
;
|
|
TargetType = linked_target(ProgramTargetType),
|
|
LinkedTargetFile = linked_target_file(ModuleName,
|
|
ProgramTargetType),
|
|
make_linked_target(Globals, LinkedTargetFile, Success, !Info, !IO)
|
|
;
|
|
TargetType = misc_target(MiscTargetType),
|
|
make_misc_target(Globals, ModuleName - MiscTargetType, Success,
|
|
!Info, !IO)
|
|
)
|
|
;
|
|
TrackFlagsSuccess = no,
|
|
Success = no
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type target_type
|
|
---> module_target(module_target_type)
|
|
; linked_target(linked_target_type)
|
|
; misc_target(misc_target_type).
|
|
|
|
:- pred classify_target(globals::in, string::in,
|
|
pair(module_name, target_type)::out) is det.
|
|
|
|
classify_target(Globals, FileName, ModuleName - TargetType) :-
|
|
(
|
|
string.length(FileName, NameLength),
|
|
search_backwards_for_dot(FileName, NameLength, DotLocn),
|
|
string.split(FileName, DotLocn, ModuleNameStr0, Suffix),
|
|
solutions(classify_target_2(Globals, ModuleNameStr0, Suffix),
|
|
TargetFiles),
|
|
TargetFiles = [TargetFile]
|
|
->
|
|
TargetFile = ModuleName - TargetType
|
|
;
|
|
string.append("lib", ModuleNameStr, FileName)
|
|
->
|
|
TargetType = misc_target(misc_target_build_library),
|
|
file_name_to_module_name(ModuleNameStr, ModuleName)
|
|
;
|
|
ExecutableType = get_executable_type(Globals),
|
|
TargetType = linked_target(ExecutableType),
|
|
file_name_to_module_name(FileName, ModuleName)
|
|
).
|
|
|
|
:- pred classify_target_2(globals::in, string::in, string::in,
|
|
pair(module_name, target_type)::out) is nondet.
|
|
|
|
classify_target_2(Globals, ModuleNameStr0, Suffix, ModuleName - TargetType) :-
|
|
(
|
|
yes(Suffix) = target_extension(Globals, ModuleTargetType),
|
|
% The .cs extension was used to build all C target files, but .cs is
|
|
% also the file name extension for a C# file. The former use is being
|
|
% migrated over to the .all_cs target but we still accept it for now.
|
|
Suffix \= ".cs"
|
|
->
|
|
ModuleNameStr = ModuleNameStr0,
|
|
TargetType = module_target(ModuleTargetType)
|
|
;
|
|
target_extension_synonym(Suffix, ModuleTargetType)
|
|
->
|
|
ModuleNameStr = ModuleNameStr0,
|
|
TargetType = module_target(ModuleTargetType)
|
|
;
|
|
globals.lookup_string_option(Globals, library_extension, Suffix),
|
|
string.append("lib", ModuleNameStr1, ModuleNameStr0)
|
|
->
|
|
ModuleNameStr = ModuleNameStr1,
|
|
TargetType = linked_target(static_library)
|
|
;
|
|
globals.lookup_string_option(Globals, shared_library_extension,
|
|
Suffix),
|
|
string.append("lib", ModuleNameStr1, ModuleNameStr0)
|
|
->
|
|
ModuleNameStr = ModuleNameStr1,
|
|
TargetType = linked_target(shared_library)
|
|
;
|
|
globals.lookup_string_option(Globals, executable_file_extension,
|
|
Suffix)
|
|
->
|
|
ModuleNameStr = ModuleNameStr0,
|
|
ExecutableType = get_executable_type(Globals),
|
|
TargetType = linked_target(ExecutableType)
|
|
;
|
|
Suffix = ".beams",
|
|
string.append("lib", ModuleNameStr1, ModuleNameStr0)
|
|
->
|
|
ModuleNameStr = ModuleNameStr1,
|
|
TargetType = linked_target(erlang_archive)
|
|
;
|
|
(
|
|
string.append(".all_", Rest, Suffix),
|
|
string.append(DotlessSuffix1, "s", Rest),
|
|
Suffix1 = "." ++ DotlessSuffix1
|
|
;
|
|
% Deprecated.
|
|
string.append(Suffix1, "s", Suffix)
|
|
),
|
|
(
|
|
yes(Suffix1) = target_extension(Globals, ModuleTargetType)
|
|
;
|
|
target_extension_synonym(Suffix1, ModuleTargetType)
|
|
),
|
|
% Not yet implemented. `build_all' targets are only used by
|
|
% tools/bootcheck, so it doesn't really matter.
|
|
ModuleTargetType \= module_target_c_header(_)
|
|
->
|
|
ModuleNameStr = ModuleNameStr0,
|
|
TargetType = misc_target(misc_target_build_all(ModuleTargetType))
|
|
;
|
|
Suffix = ".check"
|
|
->
|
|
ModuleNameStr = ModuleNameStr0,
|
|
TargetType = misc_target(misc_target_build_all(module_target_errors))
|
|
;
|
|
Suffix = ".analyse"
|
|
->
|
|
ModuleNameStr = ModuleNameStr0,
|
|
TargetType = misc_target(misc_target_build_analyses)
|
|
;
|
|
Suffix = ".clean"
|
|
->
|
|
ModuleNameStr = ModuleNameStr0,
|
|
TargetType = misc_target(misc_target_clean)
|
|
;
|
|
Suffix = ".realclean"
|
|
->
|
|
ModuleNameStr = ModuleNameStr0,
|
|
TargetType = misc_target(misc_target_realclean)
|
|
;
|
|
Suffix = ".install",
|
|
string.append("lib", ModuleNameStr1, ModuleNameStr0)
|
|
->
|
|
ModuleNameStr = ModuleNameStr1,
|
|
TargetType = misc_target(misc_target_install_library)
|
|
;
|
|
Suffix = ".doc"
|
|
->
|
|
ModuleNameStr = ModuleNameStr0,
|
|
TargetType = misc_target(misc_target_build_xml_docs)
|
|
;
|
|
fail
|
|
),
|
|
file_name_to_module_name(ModuleNameStr, ModuleName).
|
|
|
|
:- pred search_backwards_for_dot(string::in, int::in, int::out) is semidet.
|
|
|
|
search_backwards_for_dot(String, Index, DotIndex) :-
|
|
string.unsafe_prev_index(String, Index, CharIndex, Char),
|
|
( Char = ('.') ->
|
|
DotIndex = CharIndex
|
|
;
|
|
search_backwards_for_dot(String, CharIndex, DotIndex)
|
|
).
|
|
|
|
:- func get_executable_type(globals) = linked_target_type.
|
|
|
|
get_executable_type(Globals) = ExecutableType :-
|
|
globals.get_target(Globals, CompilationTarget),
|
|
(
|
|
( CompilationTarget = target_c
|
|
; CompilationTarget = target_il
|
|
; CompilationTarget = target_asm
|
|
; CompilationTarget = target_x86_64
|
|
),
|
|
ExecutableType = executable
|
|
;
|
|
CompilationTarget = target_csharp,
|
|
ExecutableType = csharp_executable
|
|
;
|
|
CompilationTarget = target_java,
|
|
ExecutableType = java_launcher
|
|
;
|
|
CompilationTarget = target_erlang,
|
|
ExecutableType = erlang_launcher
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
|
|
:- type last_hash
|
|
---> last_hash(
|
|
lh_options :: list(string),
|
|
lh_hash :: string
|
|
).
|
|
|
|
% 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(globals::in, module_name::in, bool::out,
|
|
make_info::in, make_info::out, io::di, io::uo) is det.
|
|
|
|
make_track_flags_files(Globals, ModuleName, Success, !Info, !IO) :-
|
|
find_reachable_local_modules(Globals, ModuleName, Success0, Modules,
|
|
!Info, !IO),
|
|
(
|
|
Success0 = yes,
|
|
KeepGoing = no,
|
|
DummyLastHash = last_hash([], ""),
|
|
foldl3_maybe_stop_at_error(KeepGoing, make_track_flags_files_2,
|
|
Globals, set.to_sorted_list(Modules), Success,
|
|
DummyLastHash, _LastHash, !Info, !IO)
|
|
;
|
|
Success0 = no,
|
|
Success = no
|
|
).
|
|
|
|
:- pred make_track_flags_files_2(globals::in, module_name::in, bool::out,
|
|
last_hash::in, last_hash::out, make_info::in, make_info::out,
|
|
io::di, io::uo) is det.
|
|
|
|
make_track_flags_files_2(Globals, ModuleName, Success, !LastHash, !Info,
|
|
!IO) :-
|
|
lookup_mmc_module_options(Globals, !.Info ^ options_variables, ModuleName,
|
|
OptionsResult, !IO),
|
|
(
|
|
OptionsResult = yes(ModuleOptionArgs),
|
|
OptionArgs = !.Info ^ option_args,
|
|
AllOptionArgs = list.condense([ModuleOptionArgs, 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.
|
|
( !.LastHash = last_hash(AllOptionArgs, HashPrime) ->
|
|
Hash = HashPrime
|
|
;
|
|
option_table_hash(AllOptionArgs, Hash, !IO),
|
|
!:LastHash = last_hash(AllOptionArgs, Hash)
|
|
),
|
|
|
|
module_name_to_file_name(Globals, ModuleName, ".track_flags",
|
|
do_create_dirs, HashFileName, !IO),
|
|
compare_hash_file(Globals, HashFileName, Hash, Same, !IO),
|
|
(
|
|
Same = yes,
|
|
Success = yes
|
|
;
|
|
Same = no,
|
|
write_hash_file(HashFileName, Hash, Success, !IO)
|
|
)
|
|
;
|
|
OptionsResult = no,
|
|
Success = no
|
|
).
|
|
|
|
:- pred option_table_hash(list(string)::in, string::out,
|
|
io::di, io::uo) is det.
|
|
|
|
option_table_hash(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 in a MODULE.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.
|
|
handle_given_options(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),
|
|
inconsequential_options(InconsequentialOptions),
|
|
list.filter(is_consequential_option(InconsequentialOptions),
|
|
OptionList, ConsequentialOptionList),
|
|
Hash = md4sum(string(ConsequentialOptionList)).
|
|
|
|
:- pred is_consequential_option(set(option)::in,
|
|
pair(option, option_data)::in) is semidet.
|
|
|
|
is_consequential_option(InconsequentialOptions, Option - _) :-
|
|
not set.contains(InconsequentialOptions, Option).
|
|
|
|
:- pred compare_hash_file(globals::in, string::in, string::in, bool::out,
|
|
io::di, io::uo) is det.
|
|
|
|
compare_hash_file(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),
|
|
( Line = Hash ->
|
|
Same = yes
|
|
;
|
|
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,
|
|
io.write_string("% ", !IO),
|
|
io.write_string(FileName, !IO),
|
|
(
|
|
Same = yes,
|
|
io.write_string(" does not need updating.\n", !IO)
|
|
;
|
|
Same = no,
|
|
io.write_string(" will be UPDATED.\n", !IO)
|
|
)
|
|
;
|
|
Verbose = no
|
|
).
|
|
|
|
:- pred write_hash_file(string::in, string::in, bool::out, io::di, io::uo)
|
|
is det.
|
|
|
|
write_hash_file(FileName, Hash, Success, !IO) :-
|
|
io.open_output(FileName, OpenResult, !IO),
|
|
(
|
|
OpenResult = ok(Stream),
|
|
io.write_string(Stream, Hash, !IO),
|
|
io.close_output(Stream, !IO),
|
|
Success = yes
|
|
;
|
|
OpenResult = error(Error),
|
|
io.write_string("Error creating `", !IO),
|
|
io.write_string(FileName, !IO),
|
|
io.write_string("': ", !IO),
|
|
io.write_string(io.error_message(Error), !IO),
|
|
io.nl(!IO),
|
|
Success = no
|
|
).
|
|
|
|
%-----------------------------------------------------------------------------%
|
|
:- end_module make.
|
|
%-----------------------------------------------------------------------------%
|