Files
mercury/compiler/make.module_dep_file.m
Peter Wang 181122d1f8 Fix writing .module_dep files.
compiler/make.module_dep_file.m:
    Write foreign included file names to the .module_dep stream
    instead of the implicit output stream. The bug was introduced in
    commit 5f50259d16.

tests/mmc_make/Mmakefile:
    mmc --make does not set a non-zero exit status even if there were
    problems reading a .module_dep file. Explicitly grep for "error" in
    the mmc --make output in the include_file test that should have
    caught the preceding bug (assuming the workspace is built with
    --use-subdirs).

    grep for "error" in another test case.
2021-03-01 12:28:43 +11:00

958 lines
39 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 expandtab
%-----------------------------------------------------------------------------%
% Copyright (C) 2002-2009, 2011 The University of Melbourne.
% Copyright (C) 2014-2017, 2019-2020 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.module_dep_file.m.
% Author: stayl.
%
% Code to read and write the `<module>.module_dep' files, which contain
% information about inter-module dependencies.
%
%-----------------------------------------------------------------------------%
:- module make.module_dep_file.
:- interface.
:- import_module libs.
:- import_module libs.globals.
:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module io.
:- import_module maybe.
%-----------------------------------------------------------------------------%
% Get the dependencies for a given module.
% Dependencies are generated on demand, not by a `mmc --make depend'
% command, so this predicate may need to read the source for the module.
%
:- pred get_module_dependencies(globals::in, module_name::in,
maybe(module_and_imports)::out, make_info::in, make_info::out,
io::di, io::uo) is det.
:- pred write_module_dep_file(globals::in, module_and_imports::in,
io::di, io::uo) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module libs.file_util.
:- import_module libs.process_util.
:- import_module parse_tree.
:- import_module parse_tree.error_util.
:- import_module parse_tree.file_names.
:- import_module parse_tree.item_util.
:- import_module parse_tree.mercury_to_mercury.
:- import_module parse_tree.parse_error.
:- import_module parse_tree.parse_sym_name.
:- import_module parse_tree.parse_tree_out_info.
:- import_module parse_tree.prog_data_foreign.
:- import_module parse_tree.prog_item.
:- import_module parse_tree.prog_out.
:- import_module parse_tree.read_modules.
:- import_module parse_tree.write_module_interface_files.
:- import_module cord.
:- import_module dir.
:- import_module getopt.
:- import_module parser.
:- import_module term.
:- import_module term_io.
% The version 1 module_dep file format is the same as version 2 except that
% it does not include a list of files included by `pragma foreign_decl' and
% `pragma foreign_code'. We continue to write version 1 files when
% possible.
%
% XXX We should consider
%
% - adding a version 3 that differs from 2 in deleting the field
% that now *always* contains "no_main", and
% - switching to always generating version 3.
%
% XXX The precise on-disk representation of each (current) module_dep file
% format version should be explicitly documented. This documentation
% should explain what the meaning of each field is, what purposes
% does it servce, an what invariants (if any) apply to it. It should
% also have some examples to help readers understand it all.
%
:- type module_dep_file_version
---> module_dep_file_v1
; module_dep_file_v2.
:- pred version_number(module_dep_file_version, int).
:- mode version_number(in, out) is det.
:- mode version_number(out, in) is semidet.
version_number(module_dep_file_v1, 1).
version_number(module_dep_file_v2, 2).
%-----------------------------------------------------------------------------%
get_module_dependencies(Globals, ModuleName, MaybeModuleAndImports,
!Info, !IO) :-
RebuildModuleDeps = !.Info ^ rebuild_module_deps,
(
ModuleName = unqualified(_),
maybe_get_module_dependencies(Globals, RebuildModuleDeps, ModuleName,
MaybeModuleAndImports, !Info, !IO)
;
ModuleName = qualified(_, _),
% For submodules, we need to generate the dependencies for the
% parent modules first (make_module_dependencies expects to be given
% the top-level module in a source file).
% If the module is a nested module, its dependencies will be generated
% as a side effect of generating the parent's dependencies.
AncestorsAndSelf = get_ancestors(ModuleName) ++ [ModuleName],
Error0 = no,
maybe_get_modules_dependencies(Globals, RebuildModuleDeps,
AncestorsAndSelf, Error0, !Info, !IO),
ModuleDepMap = !.Info ^ module_dependencies,
map.lookup(ModuleDepMap, ModuleName, MaybeModuleAndImports)
).
:- pred maybe_get_modules_dependencies(globals::in, rebuild_module_deps::in,
list(module_name)::in, bool::in, make_info::in, make_info::out,
io::di, io::uo) is det.
maybe_get_modules_dependencies(_Globals, _RebuildModuleDeps,
[], _, !Info, !IO).
maybe_get_modules_dependencies(Globals, RebuildModuleDeps,
[ModuleName | ModuleNames], !.Error, !Info, !IO) :-
(
!.Error = no,
maybe_get_module_dependencies(Globals, RebuildModuleDeps, ModuleName,
MaybeModuleAndImports, !Info, !IO),
(
MaybeModuleAndImports = yes(_)
;
MaybeModuleAndImports = no,
!:Error = yes
)
;
!.Error = yes,
% If we found a problem when processing an ancestor, don't even try
% to process the later modules.
ModuleDepMap0 = !.Info ^ module_dependencies,
MaybeModuleAndImports = no,
% XXX Could this be map.det_update or map.det_insert?
map.set(ModuleName, MaybeModuleAndImports,
ModuleDepMap0, ModuleDepMap),
!Info ^ module_dependencies := ModuleDepMap
),
maybe_get_modules_dependencies(Globals, RebuildModuleDeps,
ModuleNames, !.Error, !Info, !IO).
:- pred maybe_get_module_dependencies(globals::in, rebuild_module_deps::in,
module_name::in, maybe(module_and_imports)::out,
make_info::in, make_info::out, io::di, io::uo) is det.
maybe_get_module_dependencies(Globals, RebuildModuleDeps, ModuleName,
MaybeModuleAndImports, !Info, !IO) :-
ModuleDepMap0 = !.Info ^ module_dependencies,
( if map.search(ModuleDepMap0, ModuleName, MaybeModuleAndImports0) then
MaybeModuleAndImports = MaybeModuleAndImports0
else
do_get_module_dependencies(Globals, RebuildModuleDeps, ModuleName,
MaybeModuleAndImports, !Info, !IO)
).
:- pred do_get_module_dependencies(globals::in, rebuild_module_deps::in,
module_name::in, maybe(module_and_imports)::out,
make_info::in, make_info::out, io::di, io::uo) is det.
do_get_module_dependencies(Globals, RebuildModuleDeps, ModuleName,
!:MaybeModuleAndImports, !Info, !IO) :-
% We can't just use
% `get_target_timestamp(ModuleName - source, ..)'
% because that could recursively call get_module_dependencies,
% leading to an infinite loop. Just using module_name_to_file_name
% will fail if the module name doesn't match the file name, but
% that case is handled below.
module_name_to_source_file_name(ModuleName, SourceFileName, !IO),
get_file_timestamp([dir.this_directory], SourceFileName,
MaybeSourceFileTimestamp, !Info, !IO),
module_name_to_file_name(Globals, $pred, do_not_create_dirs,
ext_other(make_module_dep_file_extension),
ModuleName, DepFileName, !IO),
globals.lookup_accumulating_option(Globals, search_directories,
SearchDirs),
get_file_timestamp(SearchDirs, DepFileName, MaybeDepFileTimestamp,
!Info, !IO),
(
MaybeSourceFileTimestamp = ok(SourceFileTimestamp),
MaybeDepFileTimestamp = ok(DepFileTimestamp),
( if
( RebuildModuleDeps = do_not_rebuild_module_deps
; compare((>), DepFileTimestamp, SourceFileTimestamp)
)
then
% Since the source file was found in this directory, do not use
% module_dep files which might be for installed copies
% of the module.
%
% XXX SourceFileName may not actually be the correct source file
% for the required module. Usually the purported source file would
% have a later timestamp than the .module_dep file, though, so the
% other branch would be taken.
read_module_dependencies_no_search(Globals, RebuildModuleDeps,
ModuleName, !Info, !IO)
else
make_module_dependencies(Globals, ModuleName, !Info, !IO)
)
;
MaybeSourceFileTimestamp = error(_),
MaybeDepFileTimestamp = ok(DepFileTimestamp),
read_module_dependencies_search(Globals, RebuildModuleDeps,
ModuleName, !Info, !IO),
% Check for the case where the module name doesn't match the
% source file name (e.g. parse.m contains module mdb.parse).
% Get the correct source file name from the module dependency file,
% then check whether the module dependency file is up to date.
map.lookup(!.Info ^ module_dependencies, ModuleName,
!:MaybeModuleAndImports),
( if
!.MaybeModuleAndImports = yes(ModuleAndImports0),
module_and_imports_get_source_file_dir(ModuleAndImports0,
ModuleDir),
ModuleDir = dir.this_directory
then
module_and_imports_get_source_file_name(ModuleAndImports0,
SourceFileName1),
get_file_timestamp([dir.this_directory], SourceFileName1,
MaybeSourceFileTimestamp1, !Info, !IO),
(
MaybeSourceFileTimestamp1 = ok(SourceFileTimestamp1),
( if
( RebuildModuleDeps = do_not_rebuild_module_deps
; compare((>), DepFileTimestamp, SourceFileTimestamp1)
)
then
true
else
% XXX The existence of a .module_dep file reflects a
% previous state of the workspace which may not match the
% current workspace.
%
% Here is a (contrived) case where we run into an issue:
% 1. create prog.m which imports the standard library lexer
% module
% 2. copy the standard library lexer.m file to the current
% directory for editing
% 3. run mmc --make; it creates lexer.module_dep
% 4. change lexer.m into a submodule of prog
% 5. run mmc --make again, it no longer works
%
% The local lexer.module_dep prevents mmc --make finding
% the lexer.module_dep from the standard library, even
% though there is no longer any local source file for the
% `lexer' module.
make_module_dependencies(Globals, ModuleName, !Info, !IO)
)
;
MaybeSourceFileTimestamp1 = error(Message),
io.format("** Error reading file `%s' " ++
"to generate dependencies: %s.\n",
[s(SourceFileName1), s(Message)], !IO),
maybe_write_importing_module(ModuleName,
!.Info ^ importing_module, !IO)
)
else
true
)
;
MaybeDepFileTimestamp = error(_),
SearchDirsString = join_list(", ",
map((func(Dir) = "`" ++ Dir ++ "'"), SearchDirs)),
debug_make_msg(Globals,
io.format(
"Module dependencies file '%s' not found in directories %s.\n",
[s(DepFileName), s(SearchDirsString)]),
!IO),
% Try to make the dependencies. This will succeed when the module name
% doesn't match the file name and the dependencies for this module
% haven't been built before. It will fail if the source file
% is in another directory.
(
RebuildModuleDeps = do_rebuild_module_deps,
make_module_dependencies(Globals, ModuleName, !Info, !IO)
;
RebuildModuleDeps = do_not_rebuild_module_deps,
ModuleDepMap0 = !.Info ^ module_dependencies,
% XXX Could this be map.det_update or map.det_insert?
map.set(ModuleName, no, ModuleDepMap0, ModuleDepMap1),
!Info ^ module_dependencies := ModuleDepMap1
)
),
ModuleDepMap2 = !.Info ^ module_dependencies,
( if map.search(ModuleDepMap2, ModuleName, MaybeModuleAndImports0) then
!:MaybeModuleAndImports = MaybeModuleAndImports0
else
!:MaybeModuleAndImports = no,
map.det_insert(ModuleName, no, ModuleDepMap2, ModuleDepMap),
!Info ^ module_dependencies := ModuleDepMap
).
%-----------------------------------------------------------------------------%
write_module_dep_file(Globals, ModuleAndImports0, !IO) :-
rebuild_module_and_imports_for_dep_file(Globals,
ModuleAndImports0, ModuleAndImports),
do_write_module_dep_file(Globals, ModuleAndImports, !IO).
:- pred do_write_module_dep_file(globals::in, module_and_imports::in,
io::di, io::uo) is det.
do_write_module_dep_file(Globals, ModuleAndImports, !IO) :-
module_and_imports_get_module_name(ModuleAndImports, ModuleName),
module_name_to_file_name(Globals, $pred, do_create_dirs,
ext_other(make_module_dep_file_extension),
ModuleName, ProgDepFile, !IO),
io.open_output(ProgDepFile, ProgDepResult, !IO),
(
ProgDepResult = ok(ProgDepStream),
choose_module_dep_file_version(ModuleAndImports, Version),
do_write_module_dep_file_2(ProgDepStream, ModuleAndImports,
Version, !IO),
io.close_output(ProgDepStream, !IO)
;
ProgDepResult = error(Error),
io.error_message(Error, Msg),
io.format("Error opening %s for output: %s\n",
[s(ProgDepFile), s(Msg)], !IO),
io.set_exit_status(1, !IO)
).
:- pred choose_module_dep_file_version(module_and_imports::in,
module_dep_file_version::out) is det.
choose_module_dep_file_version(ModuleAndImports, Version) :-
module_and_imports_get_foreign_include_files(ModuleAndImports,
ForeignIncludeFilesCord),
( if cord.is_empty(ForeignIncludeFilesCord) then
Version = module_dep_file_v1
else
Version = module_dep_file_v2
).
:- pred do_write_module_dep_file_2(io.text_output_stream::in,
module_and_imports::in, module_dep_file_version::in,
io::di, io::uo) is det.
do_write_module_dep_file_2(Stream, ModuleAndImports, Version, !IO) :-
module_and_imports_get_source_file_name(ModuleAndImports, SourceFileName),
module_and_imports_get_source_file_module_name(ModuleAndImports,
SourceFileModuleName),
module_and_imports_get_ancestors(ModuleAndImports, Ancestors),
module_and_imports_get_children(ModuleAndImports, Children),
module_and_imports_get_int_deps(ModuleAndImports, IntDeps),
module_and_imports_get_imp_deps(ModuleAndImports, ImpDeps),
module_and_imports_get_nested_children(ModuleAndImports, NestedChildren),
module_and_imports_get_fact_table_deps(ModuleAndImports, FactDeps),
module_and_imports_get_contains_foreign_code(ModuleAndImports,
ContainsForeignCode),
module_and_imports_get_contains_foreign_export(ModuleAndImports,
ContainsForeignExport),
module_and_imports_get_c_j_cs_fims(ModuleAndImports, CJCsEFIMs),
module_and_imports_get_foreign_include_files(ModuleAndImports,
ForeignIncludeFiles),
io.write_string(Stream, "module(", !IO),
version_number(Version, VersionNumber),
io.write_int(Stream, VersionNumber, !IO),
io.write_string(Stream, ", """, !IO),
io.write_string(Stream, SourceFileName, !IO),
io.write_string(Stream, """,\n\t", !IO),
mercury_output_bracketed_sym_name(SourceFileModuleName, Stream, !IO),
io.write_string(Stream, ",\n\t{", !IO),
write_out_list(mercury_output_bracketed_sym_name, ", ",
set.to_sorted_list(Ancestors), Stream, !IO),
io.write_string(Stream, "},\n\t{", !IO),
write_out_list(mercury_output_bracketed_sym_name, ", ",
IntDeps, Stream, !IO),
io.write_string(Stream, "},\n\t{", !IO),
write_out_list(mercury_output_bracketed_sym_name, ", ",
ImpDeps, Stream, !IO),
io.write_string(Stream, "},\n\t{", !IO),
write_out_list(mercury_output_bracketed_sym_name, ", ",
Children, Stream, !IO),
io.write_string(Stream, "},\n\t{", !IO),
write_out_list(mercury_output_bracketed_sym_name, ", ",
set.to_sorted_list(NestedChildren), Stream, !IO),
io.write_string(Stream, "},\n\t{", !IO),
io.write_string(Stream, string.join_list(", ", FactDeps), !IO),
io.write_string(Stream, "},\n\t{", !IO),
(
ContainsForeignCode = foreign_code_langs_known(ForeignLanguageSet),
ForeignLanguages = set.to_sorted_list(ForeignLanguageSet)
;
ContainsForeignCode = foreign_code_langs_unknown,
% XXX Setting ForeignLanguages to empty when we know that
% we *don't* know its correct value looks wrong, but this
% may or may not be a bug. It is possible that execution cannot reach
% this predicate if this field in the module_and_imports structure
% has not been filled in.
% XXX CLEANUP We should try replacing this assignment with an abort.
ForeignLanguages = []
),
write_out_list(mercury_output_foreign_language_string, ", ",
ForeignLanguages, Stream, !IO),
io.write_string(Stream, "},\n\t{", !IO),
FIMSpecs = get_all_fim_specs(CJCsEFIMs),
write_out_list(write_fim_spec, ", ",
set.to_sorted_list(FIMSpecs), Stream, !IO),
io.write_string(Stream, "},\n\t", !IO),
contains_foreign_export_to_string(ContainsForeignExport,
ContainsForeignExportStr),
io.write_string(Stream, ContainsForeignExportStr, !IO),
io.write_string(Stream, ",\n\t", !IO),
% The has_main/no_main slot is not needed anymore, so we just put no_main
% in there always.
io.write_string(Stream, "no_main", !IO),
(
Version = module_dep_file_v1
;
Version = module_dep_file_v2,
io.write_string(Stream, ",\n\t{", !IO),
write_out_list(write_foreign_include_file_info, ", ",
cord.list(ForeignIncludeFiles), Stream, !IO),
io.write_string(Stream, "}", !IO)
),
io.write_string(Stream, "\n).\n", !IO).
:- pred write_fim_spec(fim_spec::in, io.text_output_stream::in,
io::di, io::uo) is det.
write_fim_spec(FIMSpec, Stream, !IO) :-
FIMSpec = fim_spec(Lang, ForeignImport),
mercury_output_foreign_language_string(Lang, Stream, !IO),
io.write_string(Stream, " - ", !IO),
mercury_output_bracketed_sym_name(ForeignImport, Stream, !IO).
:- pred write_foreign_include_file_info(foreign_include_file_info::in,
io.text_output_stream::in, io::di, io::uo) is det.
write_foreign_include_file_info(ForeignInclude, Stream, !IO) :-
ForeignInclude = foreign_include_file_info(Lang, FileName),
mercury_output_foreign_language_string(Lang, Stream, !IO),
io.write_string(Stream, " - ", !IO),
term_io.quote_string(Stream, FileName, !IO).
:- pred contains_foreign_export_to_string(contains_foreign_export, string).
:- mode contains_foreign_export_to_string(in, out) is det.
:- mode contains_foreign_export_to_string(out, in) is semidet.
contains_foreign_export_to_string(ContainsForeignExport,
ContainsForeignExportStr) :-
(
ContainsForeignExport = contains_foreign_export,
ContainsForeignExportStr = "contains_foreign_export"
;
ContainsForeignExport = contains_no_foreign_export,
% Yes, without the "contains_" prefix. Don't change it unless you mean
% to break compatibility with older .module_dep files.
ContainsForeignExportStr = "no_foreign_export"
).
%-----------------------------------------------------------------------------%
:- pred read_module_dependencies_search(globals::in, rebuild_module_deps::in,
module_name::in, make_info::in, make_info::out, io::di, io::uo) is det.
read_module_dependencies_search(Globals, RebuildModuleDeps, ModuleName,
!Info, !IO) :-
globals.lookup_accumulating_option(Globals, search_directories,
SearchDirs),
read_module_dependencies_2(Globals, RebuildModuleDeps, SearchDirs,
ModuleName, !Info, !IO).
:- pred read_module_dependencies_no_search(globals::in,
rebuild_module_deps::in, module_name::in, make_info::in, make_info::out,
io::di, io::uo) is det.
read_module_dependencies_no_search(Globals, RebuildModuleDeps, ModuleName,
!Info, !IO) :-
read_module_dependencies_2(Globals, RebuildModuleDeps,
[dir.this_directory], ModuleName, !Info, !IO).
:- pred read_module_dependencies_2(globals::in, rebuild_module_deps::in,
list(dir_name)::in, module_name::in, make_info::in, make_info::out,
io::di, io::uo) is det.
read_module_dependencies_2(Globals, RebuildModuleDeps, SearchDirs, ModuleName,
!Info, !IO) :-
module_name_to_search_file_name(Globals, $pred,
ext_other(make_module_dep_file_extension),
ModuleName, ModuleDepFile, !IO),
search_for_file_returning_dir_and_stream(SearchDirs, ModuleDepFile,
MaybeDirAndStream, !IO),
(
MaybeDirAndStream = ok(path_name_and_stream(ModuleDir, DepStream)),
parser.read_term(DepStream, TermResult, !IO),
io.close_input(DepStream, !IO),
(
TermResult = term(_, Term),
read_module_dependencies_3(Globals, SearchDirs, ModuleName,
ModuleDir, ModuleDepFile, Term, Result, !Info, !IO)
;
TermResult = eof,
Result = error("unexpected eof")
;
TermResult = error(ParseError, _),
Result = error("parse error: " ++ ParseError)
),
(
Result = ok
;
Result = error(Msg),
read_module_dependencies_remake_msg(RebuildModuleDeps,
ModuleDir ++ "/" ++ ModuleDepFile, Msg, !IO),
read_module_dependencies_remake(Globals, RebuildModuleDeps,
ModuleName, !Info, !IO)
)
;
MaybeDirAndStream = error(Msg),
debug_make_msg(Globals,
read_module_dependencies_remake_msg(RebuildModuleDeps,
ModuleDepFile, Msg),
!IO),
read_module_dependencies_remake(Globals, RebuildModuleDeps,
ModuleName, !Info, !IO)
).
:- pred read_module_dependencies_3(globals::in, list(dir_name)::in,
module_name::in, dir_name::in, file_name::in, term::in, maybe_error::out,
make_info::in, make_info::out, io::di, io::uo) is det.
read_module_dependencies_3(Globals, SearchDirs, ModuleName, ModuleDir,
ModuleDepFile, Term, Result, !Info, !IO) :-
( if
atom_term(Term, "module", ModuleArgs),
ModuleArgs = [
VersionNumberTerm,
SourceFileTerm,
SourceFileModuleNameTerm,
ParentsTerm,
IntDepsTerm,
ImpDepsTerm,
ChildrenTerm,
NestedChildrenTerm,
FactDepsTerm,
ForeignLanguagesTerm,
ForeignImportsTerm,
ContainsForeignExportTerm,
_HasMainTerm
| ModuleArgsTail
],
version_number_term(VersionNumberTerm, Version),
string_term(SourceFileTerm, SourceFileName),
try_parse_sym_name_and_no_args(SourceFileModuleNameTerm,
SourceFileModuleName),
sym_names_term(ParentsTerm, Parents),
sym_names_term(IntDepsTerm, IntDeps),
sym_names_term(ImpDepsTerm, ImpDeps),
sym_names_term(ChildrenTerm, Children),
sym_names_term(NestedChildrenTerm, NestedChildren),
braces_term(fact_dep_term, FactDepsTerm, FactDeps),
braces_term(foreign_language_term, ForeignLanguagesTerm,
ForeignLanguages),
braces_term(foreign_import_term, ForeignImportsTerm, ForeignImports),
contains_foreign_export_term(ContainsForeignExportTerm,
ContainsForeignExport),
(
Version = module_dep_file_v1,
ModuleArgsTail = [],
ForeignIncludes = []
;
Version = module_dep_file_v2,
ModuleArgsTail = [ForeignIncludesTerm],
braces_term(foreign_include_term, ForeignIncludesTerm,
ForeignIncludes)
)
then
ContainsForeignCode =
foreign_code_langs_known(set.list_to_set(ForeignLanguages)),
make_module_dep_module_and_imports(SourceFileName, ModuleDir,
SourceFileModuleName, ModuleName,
Parents, Children, NestedChildren, IntDeps, ImpDeps, FactDeps,
ForeignImports, ForeignIncludes,
ContainsForeignCode, ContainsForeignExport,
ModuleAndImports),
% Discard the module dependencies if the module is a local module
% but the source file no longer exists.
( if ModuleDir = dir.this_directory then
check_regular_file_exists(SourceFileName, SourceFileExists, !IO),
(
SourceFileExists = ok
;
SourceFileExists = error(_),
io.remove_file(ModuleDepFile, _, !IO)
)
else
SourceFileExists = ok
),
(
SourceFileExists = ok,
ModuleDepMap0 = !.Info ^ module_dependencies,
% XXX Could this be map.det_insert?
map.set(ModuleName, yes(ModuleAndImports),
ModuleDepMap0, ModuleDepMap),
!Info ^ module_dependencies := ModuleDepMap,
% Read the dependencies for the nested children. If something
% goes wrong (for example one of the files was removed), the
% dependencies for all modules in the source file will be remade
% (make_module_dependencies expects to be given the top-level
% module in the source file).
list.foldl2(
read_module_dependencies_2(Globals, do_not_rebuild_module_deps,
SearchDirs),
NestedChildren, !Info, !IO),
( if some_bad_module_dependency(!.Info, NestedChildren) then
Result = error("error in nested submodules")
else
Result = ok
)
;
SourceFileExists = error(Error),
Result = error(Error)
)
else
Result = error("failed to parse term")
).
:- pred version_number_term(term::in, module_dep_file_version::out) is semidet.
version_number_term(Term, Version) :-
decimal_term_to_int(Term, Int),
version_number(Version, Int).
:- pred string_term(term::in, string::out) is semidet.
string_term(Term, String) :-
Term = term.functor(term.string(String), [], _).
:- pred atom_term(term::in, string::out, list(term)::out) is semidet.
atom_term(Term, Atom, Args) :-
Term = term.functor(term.atom(Atom), Args, _).
:- pred braces_term(pred(term, U), term, list(U)).
:- mode braces_term(in(pred(in, out) is semidet), in, out) is semidet.
braces_term(P, Term, Args) :-
atom_term(Term, "{}", ArgTerms),
list.map(P, ArgTerms, Args).
:- pred sym_names_term(term::in, list(sym_name)::out) is semidet.
sym_names_term(Term, SymNames) :-
braces_term(try_parse_sym_name_and_no_args, Term, SymNames).
:- pred fact_dep_term(term::in, string::out) is semidet.
fact_dep_term(Term, FactDep) :-
string_term(Term, FactDep).
:- pred foreign_language_term(term::in, foreign_language::out) is semidet.
foreign_language_term(Term, Lang) :-
string_term(Term, String),
globals.convert_foreign_language(String, Lang).
:- pred foreign_import_term(term::in, fim_spec::out) is semidet.
foreign_import_term(Term, FIMSpec) :-
atom_term(Term, "-", [LanguageTerm, ImportedModuleTerm]),
foreign_language_term(LanguageTerm, Language),
try_parse_sym_name_and_no_args(ImportedModuleTerm, ImportedModuleName),
FIMSpec = fim_spec(Language, ImportedModuleName).
:- pred foreign_include_term(term::in, foreign_include_file_info::out)
is semidet.
foreign_include_term(Term, ForeignInclude) :-
atom_term(Term, "-", [LanguageTerm, FileNameTerm]),
foreign_language_term(LanguageTerm, Language),
string_term(FileNameTerm, FileName),
ForeignInclude = foreign_include_file_info(Language, FileName).
:- pred contains_foreign_export_term(term::in, contains_foreign_export::out)
is semidet.
contains_foreign_export_term(Term, ContainsForeignExport) :-
atom_term(Term, Atom, []),
contains_foreign_export_to_string(ContainsForeignExport, Atom).
:- pred some_bad_module_dependency(make_info::in, list(module_name)::in)
is semidet.
some_bad_module_dependency(Info, ModuleNames) :-
list.member(ModuleName, ModuleNames),
map.search(Info ^ module_dependencies, ModuleName, no).
:- pred check_regular_file_exists(file_name::in, maybe_error::out,
io::di, io::uo) is det.
check_regular_file_exists(FileName, FileExists, !IO) :-
FollowSymLinks = yes,
io.file_type(FollowSymLinks, FileName, ResFileType, !IO),
(
ResFileType = ok(FileType),
(
( FileType = regular_file
; FileType = unknown
),
FileExists = ok
;
( FileType = directory
; FileType = symbolic_link
; FileType = named_pipe
; FileType = socket
; FileType = character_device
; FileType = block_device
; FileType = message_queue
; FileType = semaphore
; FileType = shared_memory
),
FileExists = error(FileName ++ ": not a regular file")
)
;
ResFileType = error(Error),
FileExists = error(FileName ++ ": " ++ io.error_message(Error))
).
%-----------------------------------------------------------------------------%
% Something went wrong reading the dependencies, so just rebuild them.
%
:- pred read_module_dependencies_remake(globals::in, rebuild_module_deps::in,
module_name::in, make_info::in, make_info::out,
io::di, io::uo) is det.
read_module_dependencies_remake(Globals, RebuildModuleDeps, ModuleName,
!Info, !IO) :-
(
RebuildModuleDeps = do_rebuild_module_deps,
make_module_dependencies(Globals, ModuleName, !Info, !IO)
;
RebuildModuleDeps = do_not_rebuild_module_deps
).
:- pred read_module_dependencies_remake_msg(rebuild_module_deps::in,
string::in, string::in, io::di, io::uo) is det.
read_module_dependencies_remake_msg(RebuildModuleDeps, ModuleDepsFile, Msg,
!IO) :-
io.format("** Error reading file `%s': %s", [s(ModuleDepsFile), s(Msg)],
!IO),
(
RebuildModuleDeps = do_rebuild_module_deps,
io.write_string(" ...rebuilding\n", !IO)
;
RebuildModuleDeps = do_not_rebuild_module_deps,
io.nl(!IO)
).
% The module_name given must be the top level module in the source file.
% get_module_dependencies ensures this by making the dependencies
% for all parent modules of the requested module first.
%
:- pred make_module_dependencies(globals::in, module_name::in,
make_info::in, make_info::out, io::di, io::uo) is det.
make_module_dependencies(Globals, ModuleName, !Info, !IO) :-
prepare_to_redirect_output(ModuleName, MaybeErrorStream, !Info, !IO),
(
MaybeErrorStream = yes(ErrorStream),
io.set_output_stream(ErrorStream, OldOutputStream, !IO),
% XXX Why ask for the timestamp if we then ignore it?
read_module_src(Globals, "Getting dependencies for module",
do_not_ignore_errors, do_not_search, ModuleName, [],
SourceFileName, always_read_module(do_return_timestamp), _,
ParseTreeSrc, Specs0, ReadModuleErrors, !IO),
set.intersect(ReadModuleErrors, fatal_read_module_errors, FatalErrors),
( if set.is_non_empty(FatalErrors) then
FatalReadError = yes,
DisplayErrorReadingFile = yes
else if set.contains(ReadModuleErrors, rme_unexpected_module_name) then
% If the source file does not contain the expected module, then
% do not make the .module_dep file; it would leave a .module_dep
% file for the wrong module lying around, which the user needs
% to delete manually.
FatalReadError = yes,
DisplayErrorReadingFile = no
else
FatalReadError = no,
DisplayErrorReadingFile = no
),
(
FatalReadError = yes,
io.set_output_stream(ErrorStream, _, !IO),
write_error_specs_ignore(Globals, Specs0, !IO),
io.set_output_stream(OldOutputStream, _, !IO),
(
DisplayErrorReadingFile = yes,
io.format(
"** Error reading file `%s' to generate dependencies.\n",
[s(SourceFileName)], !IO),
maybe_write_importing_module(ModuleName,
!.Info ^ importing_module, !IO)
;
DisplayErrorReadingFile = no
),
% Display the contents of the `.err' file, then remove it
% so we don't leave `.err' files lying around for nonexistent
% modules.
globals.set_option(output_compile_error_lines, int(10000),
Globals, UnredirectGlobals),
unredirect_output(UnredirectGlobals, ModuleName, ErrorStream,
!Info, !IO),
module_name_to_file_name(Globals, $pred, do_not_create_dirs,
ext_other(other_ext(".err")), ModuleName, ErrFileName, !IO),
io.remove_file(ErrFileName, _, !IO),
ModuleDepMap0 = !.Info ^ module_dependencies,
% XXX Could this be map.det_update?
map.set(ModuleName, no, ModuleDepMap0, ModuleDepMap),
!Info ^ module_dependencies := ModuleDepMap
;
FatalReadError = no,
parse_tree_src_to_module_and_imports_list(Globals, SourceFileName,
ParseTreeSrc, ReadModuleErrors, Specs0, Specs,
RawCompUnits, ModuleAndImportsList),
SubModuleNames = list.map(raw_compilation_unit_project_name,
RawCompUnits),
io.set_output_stream(ErrorStream, _, !IO),
% XXX Why are we ignoring all previously reported errors?
io.set_exit_status(0, !IO),
write_error_specs_ignore(Globals, Specs, !IO),
io.set_output_stream(OldOutputStream, _, !IO),
list.foldl(make_info_add_module_and_imports_as_dep,
ModuleAndImportsList, !Info),
% If there were no errors, write out the `.int3' file
% while we have the contents of the module. The `int3' file
% does not depend on anything else.
globals.lookup_bool_option(Globals, very_verbose, VeryVerbose),
( if set.is_empty(ReadModuleErrors) then
Target = target_file(ModuleName, module_target_int3),
maybe_make_target_message_to_stream(Globals, OldOutputStream,
Target, !IO),
build_with_check_for_interrupt(VeryVerbose,
build_with_module_options(Globals, ModuleName,
["--make-short-interface"],
make_int3_files(ErrorStream, SourceFileName,
RawCompUnits)
),
cleanup_int3_files(Globals, SubModuleNames),
Succeeded, !Info, !IO)
else
Succeeded = no
),
build_with_check_for_interrupt(VeryVerbose,
( pred(yes::out, MakeInfo::in, MakeInfo::out,
IO0::di, IO::uo) is det :-
list.foldl(do_write_module_dep_file(Globals),
ModuleAndImportsList, IO0, IO)
),
cleanup_module_dep_files(Globals, SubModuleNames),
_Succeeded, !Info, !IO),
MadeTarget = target_file(ModuleName, module_target_int3),
record_made_target(Globals, MadeTarget,
process_module(task_make_int3), Succeeded, !Info, !IO),
unredirect_output(Globals, ModuleName, ErrorStream, !Info, !IO)
)
;
MaybeErrorStream = no
).
:- pred make_info_add_module_and_imports_as_dep(module_and_imports::in,
make_info::in, make_info::out) is det.
make_info_add_module_and_imports_as_dep(ModuleAndImports, !Info) :-
module_and_imports_get_module_name(ModuleAndImports, ModuleName),
ModuleDeps0 = !.Info ^ module_dependencies,
% XXX Could this be map.det_insert?
map.set(ModuleName, yes(ModuleAndImports), ModuleDeps0, ModuleDeps),
!Info ^ module_dependencies := ModuleDeps.
:- pred make_int3_files(io.output_stream::in, file_name::in,
list(raw_compilation_unit)::in, globals::in, list(string)::in, bool::out,
make_info::in, make_info::out, io::di, io::uo) is det.
make_int3_files(ErrorStream, SourceFileName, RawCompUnits, Globals,
_, Succeeded, !Info, !IO) :-
io.set_output_stream(ErrorStream, OutputStream, !IO),
list.foldl(write_short_interface_file_int3(Globals, SourceFileName),
RawCompUnits, !IO),
io.set_output_stream(OutputStream, _, !IO),
io.get_exit_status(ExitStatus, !IO),
Succeeded = ( if ExitStatus = 0 then yes else no ).
:- pred cleanup_int3_files(globals::in, list(module_name)::in,
make_info::in, make_info::out, io::di, io::uo) is det.
cleanup_int3_files(Globals, ModuleNames, !Info, !IO) :-
list.foldl2(cleanup_int3_file(Globals), ModuleNames, !Info, !IO).
:- pred cleanup_int3_file(globals::in, module_name::in,
make_info::in, make_info::out, io::di, io::uo) is det.
cleanup_int3_file(Globals, ModuleName, !Info, !IO) :-
make_remove_target_file_by_name(Globals, very_verbose,
ModuleName, module_target_int3, !Info, !IO).
:- pred cleanup_module_dep_files(globals::in, list(module_name)::in,
make_info::in, make_info::out, io::di, io::uo) is det.
cleanup_module_dep_files(Globals, ModuleNames, !Info, !IO) :-
list.foldl2(cleanup_module_dep_file(Globals), ModuleNames, !Info, !IO).
:- pred cleanup_module_dep_file(globals::in, module_name::in,
make_info::in, make_info::out, io::di, io::uo) is det.
cleanup_module_dep_file(Globals, ModuleName, !Info, !IO) :-
make_remove_module_file(Globals, verbose_make, ModuleName,
ext_other(make_module_dep_file_extension), !Info, !IO).
:- pred maybe_write_importing_module(module_name::in, maybe(module_name)::in,
io::di, io::uo) is det.
maybe_write_importing_module(_, no, !IO).
maybe_write_importing_module(ModuleName, yes(ImportingModuleName), !IO) :-
io.write_string("** Module `", !IO),
write_sym_name_to_cur_stream(ModuleName, !IO),
io.write_string("' is imported or included by module `", !IO),
write_sym_name_to_cur_stream(ImportingModuleName, !IO),
io.write_string("'.\n", !IO).
%-----------------------------------------------------------------------------%
:- end_module make.module_dep_file.
%-----------------------------------------------------------------------------%