mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-15 09:23:44 +00:00
The source file name that is used as the second argument of a .module_dep file
may be directory qualified (e.g. if we are using source file mapping to
include source files from multiple directories). On Windows, such directory
qualified source file names may use '\' as a path separator. We use the term
parser to read the contents of .module dep files and that requires '\' to be
escaped. However, the code that writes module_dep files does not currently
do the required escaping. Fix that.
compiler/make.module_dep_file.m:
Escape the source file name before writing it to a .module_dep file.
441 lines
17 KiB
Mathematica
441 lines
17 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, 2024-2026 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.file_util.
|
|
:- import_module libs.globals.
|
|
:- import_module mdbcomp.
|
|
:- import_module mdbcomp.sym_name.
|
|
:- import_module parse_tree.
|
|
:- import_module parse_tree.maybe_error.
|
|
:- import_module parse_tree.module_baggage.
|
|
:- import_module parse_tree.module_dep_info.
|
|
|
|
:- import_module io.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
% Exported to mercury_compile_make_hlds.m.
|
|
%
|
|
:- pred write_module_dep_file(io.text_output_stream::in, globals::in,
|
|
burdened_module::in, io::di, io::uo) is det.
|
|
|
|
% Exported to make.get_module_dep_info.m.
|
|
%
|
|
:- pred do_write_module_dep_file(io.text_output_stream::in, globals::in,
|
|
burdened_module::in, io::di, io::uo) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- pred read_module_dep_file(dir_name::in, file_name::in, string::in,
|
|
module_name::in, maybe1(module_dep_summary, string)::out) is det.
|
|
|
|
%---------------------------------------------------------------------------%
|
|
%---------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module parse_tree.file_names.
|
|
:- import_module parse_tree.get_dependencies.
|
|
:- import_module parse_tree.parse_error.
|
|
:- import_module parse_tree.parse_sym_name.
|
|
:- import_module parse_tree.parse_tree_out_misc.
|
|
:- import_module parse_tree.parse_tree_out_sym_name.
|
|
:- import_module parse_tree.prog_data_foreign.
|
|
:- import_module parse_tree.prog_item.
|
|
:- import_module parse_tree.prog_parse_tree.
|
|
|
|
:- import_module dir.
|
|
:- import_module list.
|
|
:- import_module map.
|
|
:- import_module maybe.
|
|
:- import_module mercury_term_parser.
|
|
:- import_module require.
|
|
:- import_module set.
|
|
:- import_module string.
|
|
:- import_module term.
|
|
:- import_module term_int.
|
|
:- 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 stopped generating version 1 .module_dep files
|
|
% on 2021 Jul 31.
|
|
%
|
|
% XXX We should consider
|
|
%
|
|
% - adding a version 3 that differs from 2 in deleting the field
|
|
% that now *always* contains "no_main",
|
|
% - replacing the braces that wrap the contents of each structured field
|
|
% with a function symbol that explicitly specifies the meaning of
|
|
% that contents, 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 serve, and
|
|
% - 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).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
write_module_dep_file(ProgressStream, Globals, BurdenedModule0, !IO) :-
|
|
BurdenedModule0 = burdened_module(Baggage0, ParseTreeModuleSrc),
|
|
Baggage0 = module_baggage(SourceFileName, _SourceFileDir,
|
|
SourceFileTopModuleName, MaybeTopModule,
|
|
_MaybeTimestamp, _MaybeTimestampMap, _GrabbedFileMap, _Errors),
|
|
|
|
MaybeTimestamp = maybe.no,
|
|
MaybeTimestampMap = maybe.no,
|
|
ModuleName = ParseTreeModuleSrc ^ ptms_module_name,
|
|
GrabbedFileMap = map.singleton(ModuleName, gf_src(ParseTreeModuleSrc)),
|
|
Errors = init_read_module_errors,
|
|
Baggage = module_baggage(SourceFileName, dir.this_directory,
|
|
SourceFileTopModuleName, MaybeTopModule,
|
|
MaybeTimestamp, MaybeTimestampMap, GrabbedFileMap, Errors),
|
|
|
|
BurdenedModule = burdened_module(Baggage, ParseTreeModuleSrc),
|
|
do_write_module_dep_file(ProgressStream, Globals, BurdenedModule, !IO).
|
|
|
|
do_write_module_dep_file(ProgressStream, Globals, BurdenedModule, !IO) :-
|
|
BurdenedModule = burdened_module(Baggage, ParseTreeModuleSrc),
|
|
ModuleName = ParseTreeModuleSrc ^ ptms_module_name,
|
|
% XXX LEGACY
|
|
module_name_to_file_name_create_dirs(Globals, $pred,
|
|
ext_cur_ngs(ext_cur_ngs_misc_module_dep),
|
|
ModuleName, ProgDepFile, _ProgDepFileProposed, !IO),
|
|
io.open_output(ProgDepFile, ProgDepResult, !IO),
|
|
(
|
|
ProgDepResult = ok(ProgDepStream),
|
|
do_write_module_dep_file_to_stream(ProgDepStream, Globals,
|
|
Baggage, ParseTreeModuleSrc, !IO),
|
|
io.close_output(ProgDepStream, !IO)
|
|
;
|
|
ProgDepResult = error(Error),
|
|
io.error_message(Error, Msg),
|
|
io.format(ProgressStream, "Error opening %s for output: %s\n",
|
|
[s(ProgDepFile), s(Msg)], !IO),
|
|
io.set_exit_status(1, !IO)
|
|
).
|
|
|
|
:- pred do_write_module_dep_file_to_stream(io.text_output_stream::in,
|
|
globals::in, module_baggage::in, parse_tree_module_src::in,
|
|
io::di, io::uo) is det.
|
|
|
|
do_write_module_dep_file_to_stream(Stream, Globals,
|
|
Baggage, ParseTreeModuleSrc, !IO) :-
|
|
Version = module_dep_file_v2,
|
|
version_number(Version, VersionNumber),
|
|
SourceFileName = term_io.escaped_string(Baggage ^ mb_source_file_name),
|
|
SourceFileTopModuleName = Baggage ^ mb_source_file_top_module_name,
|
|
SourceFileTopModuleNameStr =
|
|
mercury_bracketed_sym_name_to_string(SourceFileTopModuleName),
|
|
ModuleName = ParseTreeModuleSrc ^ ptms_module_name,
|
|
Ancestors = set.to_sorted_list(get_ancestors_set(ModuleName)),
|
|
IncludeMap = ParseTreeModuleSrc ^ ptms_include_map,
|
|
Children = map.keys(IncludeMap),
|
|
parse_tree_module_src_get_int_imp_deps(ParseTreeModuleSrc,
|
|
IntDepSet, ImpDepSet),
|
|
set.to_sorted_list(IntDepSet, IntDeps),
|
|
set.to_sorted_list(ImpDepSet, ImpDeps),
|
|
MaybeTopModule = Baggage ^ mb_maybe_top_module,
|
|
NestedSubModules = get_nested_children_list_of_top_module(MaybeTopModule),
|
|
get_fact_tables(ParseTreeModuleSrc, FactTableFilesSet),
|
|
FactTableFilesStrs = list.map(term_io.quoted_string,
|
|
set.to_sorted_list(FactTableFilesSet)),
|
|
globals.get_backend_foreign_languages(Globals, BackendLangsList),
|
|
BackendLangs = set.list_to_set(BackendLangsList),
|
|
get_foreign_code_langs(ParseTreeModuleSrc, CodeLangs),
|
|
get_foreign_export_langs(ParseTreeModuleSrc, ExportLangs),
|
|
set.intersect(BackendLangs, CodeLangs, BackendCodeLangs),
|
|
set.intersect(BackendLangs, ExportLangs, BackendExportLangs),
|
|
CodeLangStrs = list.map(mercury_foreign_language_to_string,
|
|
set.to_sorted_list(BackendCodeLangs)),
|
|
( if set.is_empty(BackendExportLangs) then
|
|
ContainsForeignExport = contains_no_foreign_export
|
|
else
|
|
ContainsForeignExport = contains_foreign_export
|
|
),
|
|
get_fim_specs(ParseTreeModuleSrc, FIMSpecs),
|
|
get_foreign_include_file_infos(ParseTreeModuleSrc, ForeignIncludeFiles),
|
|
FIMSpecStrs = list.map(fim_spec_to_string, set.to_sorted_list(FIMSpecs)),
|
|
FIFOStrs = list.map(foreign_include_file_info_to_string,
|
|
set.to_sorted_list(ForeignIncludeFiles)),
|
|
contains_foreign_export_to_string(ContainsForeignExport,
|
|
ContainsForeignExportStr),
|
|
io.format(Stream,
|
|
"module(%d, ""%s"",\n" ++
|
|
"\t%s,\n" ++
|
|
"\t{%s},\n" ++
|
|
"\t{%s},\n" ++
|
|
"\t{%s},\n" ++
|
|
"\t{%s},\n" ++
|
|
"\t{%s},\n" ++
|
|
"\t{%s},\n" ++
|
|
"\t{%s},\n" ++
|
|
"\t{%s},\n" ++
|
|
"\t%s,\n" ++
|
|
% The has_main/no_main slot is not needed anymore,
|
|
% so we just put no_main in there always.
|
|
"\tno_main,\n" ++
|
|
"\t{%s}\n" ++
|
|
").\n",
|
|
[i(VersionNumber), s(SourceFileName),
|
|
s(SourceFileTopModuleNameStr),
|
|
s(bracketed_sym_names_to_comma_list_string(Ancestors)),
|
|
s(bracketed_sym_names_to_comma_list_string(IntDeps)),
|
|
s(bracketed_sym_names_to_comma_list_string(ImpDeps)),
|
|
s(bracketed_sym_names_to_comma_list_string(Children)),
|
|
s(bracketed_sym_names_to_comma_list_string(NestedSubModules)),
|
|
s(string.join_list(", ", FactTableFilesStrs)),
|
|
s(string.join_list(", ", CodeLangStrs)),
|
|
s(string.join_list(", ", FIMSpecStrs)),
|
|
s(ContainsForeignExportStr),
|
|
s(string.join_list(", ", FIFOStrs))],
|
|
!IO).
|
|
|
|
:- func bracketed_sym_names_to_comma_list_string(list(sym_name)) = string.
|
|
|
|
bracketed_sym_names_to_comma_list_string(SymNames) = Str :-
|
|
Strs = list.map(mercury_bracketed_sym_name_to_string, SymNames),
|
|
Str = string.join_list(", ", Strs).
|
|
|
|
:- func fim_spec_to_string(fim_spec) = string.
|
|
|
|
fim_spec_to_string(FIMSpec) = Str :-
|
|
FIMSpec = fim_spec(Lang, ForeignImport),
|
|
LangStr = mercury_foreign_language_to_string(Lang),
|
|
ForeignImportStr = mercury_bracketed_sym_name_to_string(ForeignImport),
|
|
Str = LangStr ++ " - " ++ ForeignImportStr.
|
|
|
|
:- func foreign_include_file_info_to_string(foreign_include_file_info)
|
|
= string.
|
|
|
|
foreign_include_file_info_to_string(ForeignInclude) = Str :-
|
|
ForeignInclude = foreign_include_file_info(Lang, FileName),
|
|
LangStr = mercury_foreign_language_to_string(Lang),
|
|
Str = LangStr ++ " - " ++ term_io.quoted_string(FileName).
|
|
|
|
:- 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"
|
|
).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
|
|
read_module_dep_file(DepFileDir, DepFileName, DepFileContents, ModuleName,
|
|
Result) :-
|
|
% Since .module_dep files are automatically generated, errors in them
|
|
% should be vanishingly rare. However, this also means that if and when
|
|
% they do occur, any errors are likely to be quite obscure. In such
|
|
% cases, knowing *for sure* exactly *which* .module_dep file has
|
|
% the error would be one of the first pieces of information that
|
|
% any person looking to track down the problem would wan.
|
|
% This is why we include DepFileDir in the pathname we pass
|
|
% to read_term_from_string.
|
|
DepFilePathName = DepFileDir / DepFileName,
|
|
mercury_term_parser.read_term_from_string(DepFilePathName,
|
|
DepFileContents, _EndPos, TermResult),
|
|
(
|
|
TermResult = term(_, Term),
|
|
( if
|
|
parse_module_dep_file_term(ModuleName, DepFileDir, Term,
|
|
ModuleSummary)
|
|
then
|
|
Result = ok1(ModuleSummary)
|
|
else
|
|
Result = error1("failed to parse term")
|
|
)
|
|
;
|
|
TermResult = eof,
|
|
Result = error1("unexpected eof")
|
|
;
|
|
TermResult = error(Error, _),
|
|
Result = error1("parse error: " ++ Error)
|
|
).
|
|
|
|
:- pred parse_module_dep_file_term(module_name::in, dir_name::in, term::in,
|
|
module_dep_summary::out) is semidet.
|
|
|
|
parse_module_dep_file_term(ModuleName, DepFileDir, Term, ModuleSummary) :-
|
|
atom_term(Term, "module", ModuleArgs),
|
|
ModuleArgs = [
|
|
VersionNumberTerm,
|
|
SourceFileTerm,
|
|
SourceFileTopModuleNameTerm,
|
|
ParentsTerm, % XXX Redundant term
|
|
IntDepsTerm,
|
|
ImpDepsTerm,
|
|
ChildrenTerm,
|
|
NestedSubModulesTerm,
|
|
FactDepsTerm,
|
|
ForeignLanguagesTerm,
|
|
ForeignImportsTerm,
|
|
ContainsForeignExportTerm,
|
|
_HasMainTerm % XXX Redundant term
|
|
| ModuleArgsTail
|
|
],
|
|
|
|
version_number_term(VersionNumberTerm, Version),
|
|
string_term(SourceFileTerm, SourceFileName),
|
|
try_parse_sym_name_and_no_args(SourceFileTopModuleNameTerm,
|
|
SourceFileTopModuleName),
|
|
|
|
sym_names_term(ParentsTerm, Parents),
|
|
sym_names_term(IntDepsTerm, IntDeps),
|
|
sym_names_term(ImpDepsTerm, ImpDeps),
|
|
sym_names_term(ChildrenTerm, Children),
|
|
sym_names_term(NestedSubModulesTerm, NestedSubModules0),
|
|
|
|
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)
|
|
),
|
|
|
|
require_det (
|
|
( if ModuleName = SourceFileTopModuleName then
|
|
MaybeTopModule = top_module(set.list_to_set(NestedSubModules0))
|
|
else
|
|
MaybeTopModule = not_top_module,
|
|
expect(unify(NestedSubModules0, []), $pred,
|
|
"NestedSubModules0 != []")
|
|
),
|
|
set.list_to_set(Parents, ParentsSet),
|
|
AncestorsSet = get_ancestors_set(ModuleName),
|
|
expect(set.equal(ParentsSet, AncestorsSet), $pred,
|
|
"ParentsSet != AncestorsSet"),
|
|
ContainsForeignCode =
|
|
foreign_code_langs_known(set.list_to_set(ForeignLanguages)),
|
|
ModuleSummary = module_dep_summary(SourceFileName, DepFileDir,
|
|
SourceFileTopModuleName, ModuleName, set.list_to_set(Children),
|
|
MaybeTopModule,
|
|
set.list_to_set(IntDeps), set.list_to_set(ImpDeps),
|
|
set.list_to_set(FactDeps), set.list_to_set(ForeignImports),
|
|
set.list_to_set(ForeignIncludes),
|
|
ContainsForeignCode, ContainsForeignExport)
|
|
).
|
|
|
|
:- 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 version_number_term(term::in, module_dep_file_version::out) is semidet.
|
|
|
|
version_number_term(Term, Version) :-
|
|
term_int.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 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).
|
|
|
|
%---------------------------------------------------------------------------%
|
|
:- end_module make.module_dep_file.
|
|
%---------------------------------------------------------------------------%
|