%---------------------------------------------------------------------------% % 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.util.m. % Authors: stayl, wangp. % % Assorted predicates used to implement `mmc --make'. % %---------------------------------------------------------------------------% :- module make.util. :- interface. :- import_module backend_libs. :- import_module backend_libs.compile_target_code. :- import_module libs. :- import_module libs.file_util. :- import_module libs.globals. :- import_module libs.options. :- import_module libs.timestamp. :- import_module make.dependencies. :- import_module make.make_info. :- import_module mdbcomp. :- import_module mdbcomp.sym_name. :- import_module parse_tree. :- import_module parse_tree.file_names. :- import_module io. :- import_module list. :- import_module maybe. %---------------------------------------------------------------------------% % % File names. % % get_file_name(Globals, From, Search, TargetFile, FileName, !IO): % % Compute a file name for the given target file. % `Search' should be `do_search' if the file could be part of an % installed library. % :- pred get_file_name(globals::in, string::in, maybe_search::in, target_file::in, file_name::out, make_info::in, make_info::out, io::di, io::uo) is det. % Return the file name for the given target_file. The I/O state pair % may be needed to find this file name. % :- pred get_make_target_file_name(globals::in, string::in, target_file::in, string::out, io::di, io::uo) is det. :- pred dependency_file_to_file_name(globals::in, dependency_file::in, string::out, io::di, io::uo) is det. :- pred linked_target_file_name(globals::in, module_name::in, linked_target_type::in, file_name::out, io::di, io::uo) is det. %---------------------------------------------------------------------------% % % Extensions on file names. % :- pred extension_to_target_type(globals::in, string::in, module_target_type::out) is semidet. :- pred target_extension_synonym(string::in, module_target_type::out) is semidet. % Find the extension for the timestamp file for the given target type, % if one exists. % :- pred timestamp_extension(module_target_type::in, other_ext::out) is semidet. %---------------------------------------------------------------------------% % % Timestamp handling. % :- func init_target_file_timestamps = target_file_timestamps. % Find the timestamp updated when a target is produced. % :- pred get_timestamp_file_timestamp(globals::in, target_file::in, maybe_error(timestamp)::out, make_info::in, make_info::out, io::di, io::uo) is det. % Find the timestamp for the given dependency file. % :- pred get_dependency_timestamp(globals::in, dependency_file::in, maybe_error(timestamp)::out, make_info::in, make_info::out, io::di, io::uo) is det. % get_target_timestamp(Globals, Search, TargetFile, Timestamp) % % Find the timestamp for the given target file. % `Search' should be `do_search' if the file could be part of an % installed library. % :- pred get_target_timestamp(globals::in, maybe_search::in, target_file::in, maybe_error(timestamp)::out, make_info::in, make_info::out, io::di, io::uo) is det. % Find the timestamp of the first file matching the given % file name in one of the given directories. % :- pred get_file_timestamp(list(dir_name)::in, file_name::in, maybe_error(timestamp)::out, make_info::in, make_info::out, io::di, io::uo) is det. % If any of the inputs contain an error, return the first error. % XXX We should return all errors, not just the first. % If none of the inputs contain an error, return the oldest timestamp. % :- pred find_error_or_older_ok_timestamp(maybe_error(timestamp)::in, maybe_error(timestamp)::in, maybe_error(timestamp)::out) is det. :- pred find_error_or_oldest_ok_timestamp(list(maybe_error(timestamp))::in, maybe_error(timestamp)::out) is det. %---------------------------------------------------------------------------% % % Remove file a file, deleting the cached timestamp. % The removal is reported to the user if the given boolean option is set. % In general the option given should be `--very-verbose' when making a % `.clean' or `.realclean target', and `--verbose-make' when cleaning % after an interrupted build. % % Remove the target file and the corresponding timestamp file. % :- pred remove_make_target_file(globals::in, string::in, option::in, target_file::in, make_info::in, make_info::out, io::di, io::uo) is det. % Remove the target file and the corresponding timestamp file. % :- pred remove_make_target_file_by_name(globals::in, string::in, option::in, module_name::in, module_target_type::in, make_info::in, make_info::out, io::di, io::uo) is det. % remove_make_module_file(Globals, VerboseOption, ModuleName, Extension, % !Info, !IO). % :- pred remove_make_module_file(globals::in, option::in, module_name::in, ext::in, make_info::in, make_info::out, io::di, io::uo) is det. :- pred make_remove_file(globals::in, option::in, file_name::in, make_info::in, make_info::out, io::di, io::uo) is det. %---------------------------------------------------------------------------% :- func make_target_file_list(list(module_name), module_target_type) = list(target_file). :- func make_dependency_list(list(module_name), module_target_type) = list(dependency_file). %---------------------------------------------------------------------------% :- pred target_is_grade_or_arch_dependent(module_target_type::in) is semidet. %---------------------------------------------------------------------------% % % Debugging, verbose messages, and error messages. % % Apply the given predicate if `--debug-make' is set. % XXX Do we need this, now that we have trace goals? % :- pred debug_make_msg(globals::in, pred(io, io)::(pred(di, uo) is det), io::di, io::uo) is det. % Apply the given predicate if `--verbose-make' is set. % XXX Do we need this, now that we have trace goals? % :- pred verbose_make_msg(globals::in, pred(io, io)::(pred(di, uo) is det), io::di, io::uo) is det. % Apply the given predicate if the given boolean option is set to `yes'. % XXX Do we need this, now that we have trace goals? % :- pred verbose_make_msg_option(globals::in, option::in, pred(io, io)::(pred(di, uo) is det), io::di, io::uo) is det. % Write a debugging message relating to a given target file. % :- pred debug_file_msg(globals::in, target_file::in, string::in, io::di, io::uo) is det. % Write a message "Making " if `--verbose-make' is set. % :- pred maybe_make_linked_target_message(globals::in, file_name::in, io::di, io::uo) is det. % Write a message "Making " if `--verbose-make' is set. % :- pred maybe_make_target_message(globals::in, file_name::in, io::di, io::uo) is det. :- pred maybe_make_target_message_to_stream(globals::in, io.text_output_stream::in, file_name::in, io::di, io::uo) is det. % Write a message "Reanalysing invalid/suboptimal modules" if % `--verbose-make' is set. % :- pred maybe_reanalyse_modules_message(globals::in, io::di, io::uo) is det. % Write a message "** Error making ". % :- pred file_error(make_info::in, file_name::in, io::di, io::uo) is det. % If the given target was specified on the command line, warn that it % was already up to date. % :- pred maybe_warn_up_to_date_target(globals::in, top_target_file::in, file_name::in, make_info::in, make_info::out, io::di, io::uo) is det. % Write a message "Made symlink/copy of " % if `--verbose-make' is set. % :- pred maybe_symlink_or_copy_linked_target_message(globals::in, file_name::in, io::di, io::uo) is det. %---------------------------------------------------------------------------% % % Timing. % :- pred get_real_milliseconds(int::out, io::di, io::uo) is det. %---------------------------------------------------------------------------% % % Hash functions. % :- pred module_name_hash(module_name::in, int::out) is det. :- pred dependency_file_hash(dependency_file::in, int::out) is det. :- pred dependency_file_with_module_index_hash( dependency_file_with_module_index::in, int::out) is det. %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% :- implementation. :- import_module analysis. :- import_module make.build. :- import_module make.module_dep_file. :- import_module parse_tree.find_module. :- import_module parse_tree.module_dep_info. :- import_module parse_tree.prog_foreign. :- import_module transform_hlds. :- import_module transform_hlds.mmc_analysis. :- import_module bool. :- import_module dir. :- import_module enum. :- import_module int. :- import_module io.file. :- import_module map. :- import_module set. :- import_module string. :- import_module uint. :- import_module version_hash_table. %---------------------------------------------------------------------------% %---------------------------------------------------------------------------% get_file_name(Globals, From, Search, TargetFile, FileName, !Info, !IO) :- TargetFile = target_file(ModuleName, TargetType), ( if TargetType = module_target_source then % In some cases the module name won't match the file name % (module mdb.parse might be in parse.m or mdb.m), so we need to % look up the file name here. get_module_dependencies(Globals, ModuleName, MaybeModuleDepInfo, !Info, !IO), ( MaybeModuleDepInfo = some_module_dep_info(ModuleDepInfo), module_dep_info_get_source_file_name(ModuleDepInfo, FileName) ; MaybeModuleDepInfo = no_module_dep_info, % Something has gone wrong generating the dependencies, % so just take a punt (which probably won't work). module_name_to_source_file_name(ModuleName, FileName, !IO) ) else target_type_to_target_extension(Globals, TargetType, TargetExt), ( TargetExt = extension(Ext), ( Search = do_search, module_name_to_search_file_name(Globals, From, Ext, ModuleName, FileName, !IO) ; Search = do_not_search, module_name_to_file_name(Globals, From, do_not_create_dirs, Ext, ModuleName, FileName, !IO) ) ; ( TargetExt = foreign_obj(_, _) ; TargetExt = fact_table_obj(_, _) ), module_target_to_file_name_maybe_search(Globals, From, Search, do_not_create_dirs, TargetType, ModuleName, FileName, !IO) ) ). get_make_target_file_name(Globals, From, TargetFile, FileName, !IO) :- TargetFile = target_file(ModuleName, TargetType), module_target_to_file_name(Globals, From, do_not_create_dirs, TargetType, ModuleName, FileName, !IO). dependency_file_to_file_name(Globals, DepFile, FileName, !IO) :- ( DepFile = dep_target(TargetFile), get_make_target_file_name(Globals, $pred, TargetFile, FileName, !IO) ; DepFile = dep_file(FileName) ). linked_target_file_name(Globals, ModuleName, TargetType, FileName, !IO) :- ( TargetType = executable, globals.lookup_string_option(Globals, executable_file_extension, Ext), module_name_to_file_name(Globals, $pred, do_not_create_dirs, ext_other(other_ext(Ext)), ModuleName, FileName, !IO) ; TargetType = static_library, globals.lookup_string_option(Globals, library_extension, Ext), module_name_to_lib_file_name(Globals, $pred, do_not_create_dirs, "lib", other_ext(Ext), ModuleName, FileName, !IO) ; TargetType = shared_library, globals.lookup_string_option(Globals, shared_library_extension, Ext), module_name_to_lib_file_name(Globals, $pred, do_not_create_dirs, "lib", other_ext(Ext), ModuleName, FileName, !IO) ; TargetType = csharp_executable, module_name_to_file_name(Globals, $pred, do_not_create_dirs, ext_other(other_ext(".exe")), ModuleName, FileName, !IO) ; TargetType = csharp_library, module_name_to_file_name(Globals, $pred, do_not_create_dirs, ext_other(other_ext(".dll")), ModuleName, FileName, !IO) ; ( TargetType = java_archive ; TargetType = java_executable ), module_name_to_file_name(Globals, $pred, do_not_create_dirs, ext_other(other_ext(".jar")), ModuleName, FileName, !IO) ). :- pred module_target_to_file_name(globals::in, string::in, maybe_create_dirs::in, module_target_type::in, module_name::in, file_name::out, io::di, io::uo) is det. module_target_to_file_name(Globals, From, MkDir, TargetType, ModuleName, FileName, !IO) :- module_target_to_file_name_maybe_search(Globals, From, do_not_search, MkDir, TargetType, ModuleName, FileName, !IO). :- pred module_target_to_file_name_maybe_search(globals::in, string::in, maybe_search::in, maybe_create_dirs::in, module_target_type::in, module_name::in, file_name::out, io::di, io::uo) is det. module_target_to_file_name_maybe_search(Globals, From, Search, MkDir, TargetType, ModuleName, FileName, !IO) :- target_type_to_target_extension(Globals, TargetType, TargetExt), ( TargetExt = extension(Ext), ( Search = do_search, module_name_to_search_file_name(Globals, From, Ext, ModuleName, FileName, !IO) ; Search = do_not_search, module_name_to_file_name(Globals, From, MkDir, Ext, ModuleName, FileName, !IO) ) ; TargetExt = foreign_obj(PIC, Lang), foreign_language_module_name(ModuleName, Lang, ForeignModuleName), module_target_to_file_name_maybe_search(Globals, From, Search, MkDir, module_target_object_code(PIC), ForeignModuleName, FileName, !IO) ; TargetExt = fact_table_obj(PIC, FactFile), pic_object_file_extension(Globals, PIC, OtherExt), fact_table_file_name(Globals, $pred, MkDir, OtherExt, FactFile, FileName, !IO) ). %---------------------------------------------------------------------------% :- type target_extension ---> extension(ext) ; foreign_obj(pic, foreign_language) ; fact_table_obj(pic, string). :- pred target_type_to_target_extension(globals::in, module_target_type::in, target_extension::out) is det. target_type_to_target_extension(Globals, Target, TargetExt) :- % target_type_to_extension and extension_to_target_type represent % the same relationship between targets and suffixes, but in different % directions. Their codes should be kept in sync. require_complete_switch [Target] ( Target = module_target_source, TargetExt = extension(ext_src) ; Target = module_target_errors, TargetExt = extension(ext_other(other_ext(".err"))) ; Target = module_target_int0, TargetExt = extension(ext_other(other_ext(".int0"))) ; Target = module_target_int1, TargetExt = extension(ext_other(other_ext(".int"))) ; Target = module_target_int2, TargetExt = extension(ext_other(other_ext(".int2"))) ; Target = module_target_int3, TargetExt = extension(ext_other(other_ext(".int3"))) ; Target = module_target_opt, TargetExt = extension(ext_other(other_ext(".opt"))) ; Target = module_target_analysis_registry, TargetExt = extension(ext_other(other_ext(".analysis"))) ; Target = module_target_track_flags, TargetExt = extension(ext_other(other_ext(".track_flags"))) ; Target = module_target_c_header(header_mih), TargetExt = extension(ext_other(other_ext(".mih"))) ; Target = module_target_c_header(header_mh), TargetExt = extension(ext_other(other_ext(".mh"))) ; Target = module_target_c_code, TargetExt = extension(ext_other(other_ext(".c"))) ; Target = module_target_csharp_code, % XXX ".exe" if the module contains main. TargetExt = extension(ext_other(other_ext(".cs"))) ; Target = module_target_java_code, TargetExt = extension(ext_other(other_ext(".java"))) ; Target = module_target_java_class_code, TargetExt = extension(ext_other(other_ext(".class"))) ; Target = module_target_object_code(PIC), pic_object_file_extension(Globals, PIC, OtherExt), TargetExt = extension(ext_other(OtherExt)) ; Target = module_target_xml_doc, TargetExt = extension(ext_other(other_ext(".xml"))) ; Target = module_target_foreign_object(PIC, Lang), TargetExt = foreign_obj(PIC, Lang) ; Target = module_target_fact_table_object(PIC, FactFile), TargetExt = fact_table_obj(PIC, FactFile) ). extension_to_target_type(Globals, ExtStr, Target) :- % target_type_to_extension and extension_to_target_type represent % the same relationship between targets and suffixes, but in different % directions. Their codes should be kept in sync. ( if ( ExtStr = ".m", TargetPrime = module_target_source ; ExtStr = ".err", TargetPrime = module_target_errors ; ExtStr = ".int0", TargetPrime = module_target_int0 ; ExtStr = ".int", TargetPrime = module_target_int1 ; ExtStr = ".int2", TargetPrime = module_target_int2 ; ExtStr = ".int3", TargetPrime = module_target_int3 ; ExtStr = ".opt", TargetPrime = module_target_opt ; ExtStr = ".analysis", TargetPrime = module_target_analysis_registry ; ExtStr = ".track_flags", TargetPrime = module_target_track_flags ; ExtStr = ".mih", TargetPrime = module_target_c_header(header_mih) ; ExtStr = ".mh", TargetPrime = module_target_c_header(header_mh) ; ExtStr = ".c", TargetPrime = module_target_c_code ; ExtStr = ".cs", TargetPrime = module_target_csharp_code ; ExtStr = ".java", TargetPrime = module_target_java_code ; ExtStr = ".class", TargetPrime = module_target_java_class_code ; ExtStr = ".xml", TargetPrime = module_target_xml_doc ) then Target = TargetPrime else if is_pic_object_file_extension(Globals, ExtStr, PIC) then Target = module_target_object_code(PIC) else fail ). target_extension_synonym(".csharp", module_target_csharp_code). % Currently the ".cs" extension is still treated as the build-all target % for C files, so we accept ".csharp" for C# files. timestamp_extension(ModuleTargetType, other_ext(ExtStr)) :- ( ModuleTargetType = module_target_errors, % We need a timestamp file for `.err' files because errors are written % to the `.err' file even when writing interfaces. The timestamp % is only updated when compiling to target code. ExtStr = ".err_date" ; ModuleTargetType = module_target_int0, ExtStr = ".date0" ; ModuleTargetType = module_target_int1, ExtStr = ".date" ; ModuleTargetType = module_target_int2, ExtStr = ".date" ; ModuleTargetType = module_target_int3, ExtStr = ".date3" ; ModuleTargetType = module_target_opt, ExtStr = ".optdate" ; ModuleTargetType = module_target_analysis_registry, % We need a timestamp file for `.analysis' files because they % can be modified in the process of analysing _another_ module. % The timestamp is only updated after actually analysing the module % that the `.analysis' file corresponds to. ExtStr = ".analysis_date" ; % Header files share a timestamp file with their corresponding % target code files. ( ModuleTargetType = module_target_c_code ; ModuleTargetType = module_target_c_header(_) ), ExtStr = ".c_date" ; ModuleTargetType = module_target_csharp_code, ExtStr = ".cs_date" ; ModuleTargetType = module_target_java_code, ExtStr = ".java_date" ). %---------------------------------------------------------------------------% init_target_file_timestamps = version_hash_table.unsafe_init_default(target_file_hash). %---------------------% get_timestamp_file_timestamp(Globals, target_file(ModuleName, TargetType), MaybeTimestamp, !Info, !IO) :- ( if timestamp_extension(TargetType, TimestampOtherExt) then module_name_to_file_name(Globals, $pred, do_not_create_dirs, ext_other(TimestampOtherExt), ModuleName, FileName, !IO) else module_target_to_file_name(Globals, $pred, do_not_create_dirs, TargetType, ModuleName, FileName, !IO) ), % We should only ever look for timestamp files in the current directory. % Timestamp files are only used when processing a module, and only modules % in the current directory are processed. SearchDirs = [dir.this_directory], get_file_timestamp(SearchDirs, FileName, MaybeTimestamp, !Info, !IO). %---------------------% get_dependency_timestamp(Globals, DependencyFile, MaybeTimestamp, !Info, !IO) :- ( DependencyFile = dep_file(FileName), SearchDirs = [dir.this_directory], get_file_timestamp(SearchDirs, FileName, MaybeTimestamp, !Info, !IO) ; DependencyFile = dep_target(Target), get_target_timestamp(Globals, do_search, Target, MaybeTimestamp0, !Info, !IO), ( if Target = target_file(_, module_target_c_header(header_mih)), MaybeTimestamp0 = ok(_) then % Don't rebuild the `.o' file if an irrelevant part of a % `.mih' file has changed. If a relevant part of a `.mih' % file changed, the interface files of the imported module % must have changed in a way that would force the `.c' and % `.o' files of the current module to be rebuilt. MaybeTimestamp = ok(oldest_timestamp) else MaybeTimestamp = MaybeTimestamp0 ) ). %---------------------% get_target_timestamp(Globals, Search, TargetFile, MaybeTimestamp, !Info, !IO) :- TargetFile = target_file(_ModuleName, TargetType), ( if TargetType = module_target_analysis_registry then get_file_name(Globals, $pred, Search, TargetFile, FileName, !Info, !IO), get_target_timestamp_analysis_registry(Globals, Search, TargetFile, FileName, MaybeTimestamp, !Info, !IO) else % This path is hit very frequently so it is worth caching timestamps by % target_file. It avoids having to compute a file name for a % target_file first, before looking up the timestamp for that file. TargetFileTimestamps0 = !.Info ^ mki_target_file_timestamps, ( if version_hash_table.search(TargetFileTimestamps0, TargetFile, Timestamp) then MaybeTimestamp = ok(Timestamp) else get_file_name(Globals, $pred, Search, TargetFile, FileName, !Info, !IO), get_target_timestamp_2(Globals, Search, TargetFile, FileName, MaybeTimestamp, !Info, !IO), ( MaybeTimestamp = ok(Timestamp), TargetFileTimestamps1 = !.Info ^ mki_target_file_timestamps, version_hash_table.det_insert(TargetFile, Timestamp, TargetFileTimestamps1, TargetFileTimestamps), !Info ^ mki_target_file_timestamps := TargetFileTimestamps ; MaybeTimestamp = error(_) % Do not record errors. These would usually be due to files not % yet made, and the result would have to be updated once the % file is made. ) ) ). % Special treatment for `.analysis' files. If the corresponding % `.analysis_status' file says the `.analysis' file is invalid then we % treat it as out of date. % :- pred get_target_timestamp_analysis_registry(globals::in, maybe_search::in, target_file::in, file_name::in, maybe_error(timestamp)::out, make_info::in, make_info::out, io::di, io::uo) is det. get_target_timestamp_analysis_registry(Globals, Search, TargetFile, FileName, MaybeTimestamp, !Info, !IO) :- TargetFile = target_file(ModuleName, _TargetType), FileTimestamps0 = !.Info ^ mki_file_timestamps, ( if map.search(FileTimestamps0, FileName, MaybeTimestamp0) then MaybeTimestamp = MaybeTimestamp0 else do_read_module_overall_status(mmc, Globals, ModuleName, Status, !IO), ( ( Status = optimal ; Status = suboptimal ), get_target_timestamp_2(Globals, Search, TargetFile, FileName, MaybeTimestamp, !Info, !IO) ; Status = invalid, MaybeTimestamp = error("invalid module"), map.det_insert(FileName, MaybeTimestamp, FileTimestamps0, FileTimestamps), !Info ^ mki_file_timestamps := FileTimestamps ) ). :- pred get_target_timestamp_2(globals::in, maybe_search::in, target_file::in, file_name::in, maybe_error(timestamp)::out, make_info::in, make_info::out, io::di, io::uo) is det. get_target_timestamp_2(Globals, Search, TargetFile, FileName, MaybeTimestamp, !Info, !IO) :- TargetFile = target_file(ModuleName, TargetType), ( Search = do_search, get_search_directories(Globals, TargetType, SearchDirs) ; Search = do_not_search, SearchDirs = [dir.this_directory] ), get_file_timestamp(SearchDirs, FileName, MaybeTimestamp0, !Info, !IO), ( if MaybeTimestamp0 = error(_), ( TargetType = module_target_opt ; TargetType = module_target_analysis_registry ) then % If a `.opt' file in another directory doesn't exist, % it just means that a library wasn't compiled with % `--intermodule-optimization'. % Similarly for `.analysis' files. get_module_dependencies(Globals, ModuleName, MaybeModuleDepInfo, !Info, !IO), ( if MaybeModuleDepInfo = some_module_dep_info(ModuleDepInfo), module_dep_info_get_source_file_dir(ModuleDepInfo, ModuleDir), ModuleDir \= dir.this_directory then MaybeTimestamp = ok(oldest_timestamp), FileTimestamps0 = !.Info ^ mki_file_timestamps, map.set(FileName, MaybeTimestamp, FileTimestamps0, FileTimestamps), !Info ^ mki_file_timestamps := FileTimestamps else MaybeTimestamp = MaybeTimestamp0 ) else MaybeTimestamp = MaybeTimestamp0 ). :- pred get_search_directories(globals::in, module_target_type::in, list(dir_name)::out) is det. get_search_directories(Globals, TargetType, SearchDirs) :- MaybeOpt = search_for_file_type(TargetType), ( MaybeOpt = yes(SearchDirOpt), globals.lookup_accumulating_option(Globals, SearchDirOpt, SearchDirs0), % Make sure the current directory is searched for C headers % and libraries. ( if list.member(dir.this_directory, SearchDirs0) then SearchDirs = SearchDirs0 else SearchDirs = [dir.this_directory | SearchDirs0] ) ; MaybeOpt = no, SearchDirs = [dir.this_directory] ). :- func search_for_file_type(module_target_type) = maybe(option). search_for_file_type(ModuleTargetType) = MaybeSearchOption :- ( ( ModuleTargetType = module_target_source ; ModuleTargetType = module_target_errors ; ModuleTargetType = module_target_track_flags ; ModuleTargetType = module_target_c_code ; ModuleTargetType = module_target_csharp_code ; ModuleTargetType = module_target_java_code ; ModuleTargetType = module_target_java_class_code ; ModuleTargetType = module_target_object_code(_) ; ModuleTargetType = module_target_foreign_object(_, _) ; ModuleTargetType = module_target_fact_table_object(_, _) ; ModuleTargetType = module_target_xml_doc ), MaybeSearchOption = no ; ( ModuleTargetType = module_target_int0 ; ModuleTargetType = module_target_int1 ; ModuleTargetType = module_target_int2 ; ModuleTargetType = module_target_int3 ), MaybeSearchOption = yes(search_directories) ; ( ModuleTargetType = module_target_opt ; ModuleTargetType = module_target_analysis_registry ), MaybeSearchOption = yes(intermod_directories) ; ModuleTargetType = module_target_c_header(_), MaybeSearchOption = yes(c_include_directory) ). %---------------------% get_file_timestamp(SearchDirs, FileName, MaybeTimestamp, !Info, !IO) :- FileTimestamps0 = !.Info ^ mki_file_timestamps, ( if map.search(FileTimestamps0, FileName, MaybeTimestamp0) then MaybeTimestamp = MaybeTimestamp0 else search_for_file_mod_time(SearchDirs, FileName, SearchResult, !IO), ( SearchResult = ok(TimeT), Timestamp = time_t_to_timestamp(TimeT), MaybeTimestamp = ok(Timestamp), map.det_insert(FileName, MaybeTimestamp, FileTimestamps0, FileTimestamps), !Info ^ mki_file_timestamps := FileTimestamps ; SearchResult = error(_SearchError), % XXX MAKE We should not ignore _SearchError. string.format("file `%s' not found", [s(FileName)], NotFoundMsg), MaybeTimestamp = error(NotFoundMsg) ) ). %---------------------% find_error_or_older_ok_timestamp(MaybeTimestampA, MaybeTimestampB, MaybeTimestamp) :- ( MaybeTimestampA = error(_), MaybeTimestamp = MaybeTimestampA ; MaybeTimestampA = ok(TimestampA), ( MaybeTimestampB = error(_), MaybeTimestamp = MaybeTimestampB ; MaybeTimestampB = ok(TimestampB), ( if compare((<), TimestampA, TimestampB) then Timestamp = TimestampA else Timestamp = TimestampB ), MaybeTimestamp = ok(Timestamp) ) ). %---------------------% find_error_or_oldest_ok_timestamp(MaybeTimestamps, MaybeTimestamp) :- list.foldl(find_error_or_older_ok_timestamp, MaybeTimestamps, ok(newest_timestamp), MaybeTimestamp). %---------------------------------------------------------------------------% remove_make_target_file(Globals, From, VerboseOption, Target, !Info, !IO) :- Target = target_file(ModuleName, TargetType), remove_make_target_file_by_name(Globals, From, VerboseOption, ModuleName, TargetType, !Info, !IO). %---------------------% remove_make_target_file_by_name(Globals, From, VerboseOption, ModuleName, TargetType, !Info, !IO) :- module_target_to_file_name(Globals, From, do_not_create_dirs, TargetType, ModuleName, FileName, !IO), make_remove_file(Globals, VerboseOption, FileName, !Info, !IO), ( if timestamp_extension(TargetType, TimestampOtherExt) then remove_make_module_file(Globals, VerboseOption, ModuleName, ext_other(TimestampOtherExt), !Info, !IO) else true ). %---------------------% remove_make_module_file(Globals, VerboseOption, ModuleName, Ext, !Info, !IO) :- module_name_to_file_name(Globals, $pred, do_not_create_dirs, Ext, ModuleName, FileName, !IO), make_remove_file(Globals, VerboseOption, FileName, !Info, !IO). %---------------------% make_remove_file(Globals, VerboseOption, FileName, !Info, !IO) :- verbose_make_msg_option(Globals, VerboseOption, report_remove_file(FileName), !IO), io.file.remove_file_recursively(FileName, _, !IO), FileTimestamps0 = !.Info ^ mki_file_timestamps, map.delete(FileName, FileTimestamps0, FileTimestamps), !Info ^ mki_file_timestamps := FileTimestamps, % For simplicity, clear out all target file timestamps. !Info ^ mki_target_file_timestamps := init_target_file_timestamps. :- pred report_remove_file(string::in, io::di, io::uo) is det. report_remove_file(FileName, !IO) :- io.format("Removing %s\n", [s(FileName)], !IO). %---------------------------------------------------------------------------% make_target_file_list(ModuleNames, TargetType) = list.map((func(ModuleName) = target_file(ModuleName, TargetType)), ModuleNames). make_dependency_list(ModuleNames, TargetType) = list.map((func(Module) = dep_target(target_file(Module, TargetType))), ModuleNames). %---------------------------------------------------------------------------% target_is_grade_or_arch_dependent(Target) :- is_target_grade_or_arch_dependent(Target) = yes. :- func is_target_grade_or_arch_dependent(module_target_type) = bool. is_target_grade_or_arch_dependent(Target) = IsDependent :- ( ( Target = module_target_source ; Target = module_target_errors ; Target = module_target_int0 ; Target = module_target_int1 ; Target = module_target_int2 ; Target = module_target_int3 ; Target = module_target_c_header(header_mh) ; Target = module_target_xml_doc ), IsDependent = no ; ( Target = module_target_opt ; Target = module_target_analysis_registry ; Target = module_target_track_flags ; Target = module_target_c_header(header_mih) ; Target = module_target_c_code ; Target = module_target_csharp_code ; Target = module_target_java_code ; Target = module_target_java_class_code ; Target = module_target_object_code(_) ; Target = module_target_foreign_object(_, _) ; Target = module_target_fact_table_object(_, _) ), IsDependent = yes ). %---------------------------------------------------------------------------% debug_make_msg(Globals, P, !IO) :- verbose_make_msg_option(Globals, debug_make, P, !IO). verbose_make_msg(Globals, P, !IO) :- verbose_make_msg_option(Globals, verbose_make, P, !IO). verbose_make_msg_option(Globals, Option, P, !IO) :- globals.lookup_bool_option(Globals, Option, OptionValue), ( OptionValue = yes, P(!IO), io.flush_output(!IO) ; OptionValue = no ). debug_file_msg(Globals, TargetFile, Msg, !IO) :- % XXX MAKE_FILENAME If you want to log that you are doing something % to a target file, you must already know its name, so why get % get_make_target_file_name to compute it again? debug_make_msg(Globals, ( pred(!.IO::di, !:IO::uo) is det :- get_make_target_file_name(Globals, $pred, TargetFile, FileName, !IO), io.format("%s: %s\n", [s(FileName), s(Msg)], !IO) ), !IO). maybe_make_linked_target_message(Globals, FileName, !IO) :- verbose_make_msg(Globals, ( pred(!.IO::di, !:IO::uo) is det :- % Write this with one call to avoid interleaved output % when doing parallel builds. string.format("Making %s\n", [s(FileName)], Msg), % XXX The lack of a specified stream here is probably a bug. io.write_string(Msg, !IO) ), !IO). maybe_make_target_message(Globals, FileName, !IO) :- io.output_stream(OutputStream, !IO), maybe_make_target_message_to_stream(Globals, OutputStream, FileName, !IO). maybe_make_target_message_to_stream(Globals, OutputStream, FileName, !IO) :- verbose_make_msg(Globals, ( pred(!.IO::di, !:IO::uo) is det :- % XXX MAKE_STREAM % Try to write this with one call to avoid interleaved output % when doing parallel builds. string.format("Making %s\n", [s(FileName)], Msg), io.write_string(OutputStream, Msg, !IO) ), !IO). maybe_reanalyse_modules_message(Globals, !IO) :- verbose_make_msg(Globals, ( pred(!.IO::di, !:IO::uo) is det :- % XXX MAKE_STREAM io.output_stream(OutputStream, !IO), io.write_string(OutputStream, "Reanalysing invalid/suboptimal modules\n", !IO) ), !IO). file_error(Info, FileName, !IO) :- string.format("** Error making `%s'.\n", [s(FileName)], Msg), % XXX MAKE_STREAM with_locked_stdout(Info, io.write_string(Msg), !IO). maybe_warn_up_to_date_target(Globals, Target, FileName, !Info, !IO) :- globals.lookup_bool_option(Globals, warn_up_to_date, Warn), CmdLineTargets0 = !.Info ^ mki_command_line_targets, ( Warn = yes, ( if set.member(Target, CmdLineTargets0) then io.format("** Nothing to be done for `%s'.\n", [s(FileName)], !IO) else true ) ; Warn = no ), set.delete(Target, CmdLineTargets0, CmdLineTargets), !Info ^ mki_command_line_targets := CmdLineTargets. maybe_symlink_or_copy_linked_target_message(Globals, FileName, !IO) :- verbose_make_msg(Globals, ( pred(!.IO::di, !:IO::uo) is det :- io.format("Made symlink/copy of %s\n", [s(FileName)], !IO) ), !IO). %---------------------------------------------------------------------------% % % Timing. % :- pragma foreign_proc("C", get_real_milliseconds(Time::out, _IO0::di, _IO::uo), [will_not_call_mercury, promise_pure, thread_safe, tabled_for_io], " Time = MR_get_real_milliseconds(); "). :- pragma foreign_proc("C#", get_real_milliseconds(Time::out, _IO0::di, _IO::uo), [will_not_call_mercury, promise_pure, thread_safe, tabled_for_io], " Time = System.Environment.TickCount; "). :- pragma foreign_proc("Java", get_real_milliseconds(Time::out, _IO0::di, _IO::uo), [will_not_call_mercury, promise_pure, thread_safe, tabled_for_io], " // The loss of precision is acceptable for mmc --make. Time = (int) System.currentTimeMillis(); "). get_real_milliseconds(_, _, _) :- sorry($file, $pred). %---------------------------------------------------------------------------% % % Hash functions. % module_name_hash(SymName, Hash) :- ( SymName = unqualified(Name), Hash = string.hash(Name) ; SymName = qualified(_ModuleName, Name), % Hashing the module name seems to be not worthwhile. Hash = string.hash(Name) ). dependency_file_hash(DepFile, Hash) :- ( DepFile = dep_target(TargetFile), target_file_hash(TargetFile, Hash) ; DepFile = dep_file(FileName), Hash = string.hash(FileName) ). dependency_file_with_module_index_hash(DepFile, Hash) :- ( DepFile = dfmi_target(ModuleIndex, Type), Hash0 = cast_to_int(to_uint(ModuleIndex)), Hash1 = module_target_type_to_nonce(Type), Hash = mix(Hash0, Hash1) ; DepFile = dfmi_file(FileName), Hash = string.hash(FileName) ). :- pred target_file_hash(target_file::in, int::out) is det. target_file_hash(TargetFile, Hash) :- TargetFile = target_file(ModuleName, Type), module_name_hash(ModuleName, Hash0), Hash1 = module_target_type_to_nonce(Type), Hash = mix(Hash0, Hash1). :- func module_target_type_to_nonce(module_target_type) = int. module_target_type_to_nonce(Type) = X :- ( Type = module_target_source, X = 1 ; Type = module_target_errors, X = 2 ; Type = module_target_int0, X = 3 ; Type = module_target_int1, X = 4 ; Type = module_target_int2, X = 5 ; Type = module_target_int3, X = 6 ; Type = module_target_opt, X = 7 ; Type = module_target_analysis_registry, X = 8 ; Type = module_target_c_header(header_mh), X = 9 ; Type = module_target_c_header(header_mih), X = 10 ; Type = module_target_c_code, X = 11 ; Type = module_target_java_code, X = 12 % ; % Type = module_target_erlang_header, % X = 13 % ; % Type = module_target_erlang_code, % X = 14 % ; % Type = module_target_erlang_beam_code, % X = 15 ; Type = module_target_object_code(PIC), X = 16 `mix` pic_to_nonce(PIC) ; Type = module_target_foreign_object(_PIC, _ForeignLang), X = 17 ; Type = module_target_fact_table_object(_PIC, _FileName), X = 18 ; Type = module_target_xml_doc, X = 19 ; Type = module_target_track_flags, X = 20 ; Type = module_target_java_class_code, X = 21 ; Type = module_target_csharp_code, X = 22 ). :- func pic_to_nonce(pic) = int. pic_to_nonce(pic) = 1. pic_to_nonce(non_pic) = 3. % For compatibility; we used to have pic_to_nonce(link_with_pic) = 2. :- func mix(int, int) = int. mix(H0, X) = H :- H1 = H0 `xor` (H0 `unchecked_left_shift` 5), H = H1 `xor` X. %---------------------------------------------------------------------------% :- end_module make.util. %---------------------------------------------------------------------------%