Files
mercury/compiler/make.m
Peter Wang d2e304c7a8 Prevent interleaved error message output when using parallel `mmc --make'.
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.
2012-05-11 03:56:48 +00:00

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.
%-----------------------------------------------------------------------------%