Files
mercury/compiler/module_cmds.m
2024-12-14 20:05:13 +11:00

810 lines
31 KiB
Mathematica

%-----------------------------------------------------------------------------%
% vim: ft=mercury ts=4 sw=4 et
%-----------------------------------------------------------------------------%
% Copyright (C) 2008-2012 The University of Melbourne.
% Copyright (C) 2013-2024 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: module_cmds.m.
%
% This module handles the most of the commands generated by the
% parse_tree package.
%
%-----------------------------------------------------------------------------%
:- module parse_tree.module_cmds.
:- interface.
:- import_module libs.
:- import_module libs.file_util.
:- import_module libs.globals.
:- import_module libs.maybe_util.
:- import_module mdbcomp.
:- import_module mdbcomp.sym_name.
:- import_module parse_tree.file_names.
:- import_module io.
:- import_module list.
%-----------------------------------------------------------------------------%
% copy_dot_tmp_to_base_file_return_succeeded(ProgressStream, Globals,
% ModuleName, OutputFileName, Succeeded, !IO)
%
% Update the interface file FileName from FileName.tmp if it has changed,
% and return whether the update succeeded (if it was needed).
%
:- pred copy_dot_tmp_to_base_file_return_succeeded(io.text_output_stream::in,
globals::in, file_name::in, maybe_succeeded::out, io::di, io::uo) is det.
% copy_dot_tmp_to_base_file_report_any_error(ProgressStream, Globals,
% FileKindStr, OutputFileName, Succeeded, !IO)
%
% As copy_dot_tmp_to_base_file_return_succeeded, but also print
% an error message (which includes FileKindStr) if the update failed.
%
:- pred copy_dot_tmp_to_base_file_report_any_error(io.text_output_stream::in,
globals::in, string::in, file_name::in, maybe_succeeded::out,
io::di, io::uo) is det.
:- type dot_tmp_copy_result
---> base_file_new_or_changed
; base_file_unchanged
; dot_tmp_copy_error.
% copy_dot_tmp_to_base_file_return_changed(ProgressStream, Globals,
% FileName, Result, !IO):
%
% Update the interface file FileName from FileName.tmp if it has changed.
% Report whether the update was needed, and if it was, whether it
% succeeded.
%
:- pred copy_dot_tmp_to_base_file_return_changed(io.text_output_stream::in,
globals::in, file_name::in, dot_tmp_copy_result::out,
io::di, io::uo) is det.
%-----------------------------------------------------------------------------%
% maybe_make_symlink(Globals, TargetFile, LinkName, Result, !IO):
%
% If `--use-symlinks' is set, attempt to make LinkName a symlink
% pointing to LinkTarget.
%
:- pred maybe_make_symlink(globals::in, file_name::in, file_name::in,
maybe_succeeded::out, io::di, io::uo) is det.
% definitely_make_symlink(TargetFile, LinkName, Result, !IO):
%
% Attempt to make LinkName a symlink pointing to LinkTarget.
% Assumes that the caller has checked that `--use-symlinks' is set.
%
:- pred definitely_make_symlink(file_name::in, file_name::in,
maybe_succeeded::out, io::di, io::uo) is det.
% make_symlink_or_copy_file(Globals, ProgressStream, LinkTarget, LinkName,
% Succeeded, !IO):
%
% Attempt to make LinkName a symlink pointing to LinkTarget, copying
% LinkTarget to LinkName if that fails (or if `--use-symlinks' is not set).
%
:- pred make_symlink_or_copy_file(globals::in, io.text_output_stream::in,
file_name::in, file_name::in, maybe_succeeded::out, io::di, io::uo) is det.
%-----------------------------------------------------------------------------%
% touch_module_ext_datestamp(Globals, ProgressStream,
% ModuleName, Ext, Succeeded, !IO):
%
% Touch the datestamp file `ModuleName.Ext'. Datestamp files are used
% to record when each of the interface files was last updated.
%
:- pred touch_module_ext_datestamp(globals::in, io.text_output_stream::in,
module_name::in, ext::in, maybe_succeeded::out, io::di, io::uo) is det.
% touch_file_datestamp(Globals, ProgressStream, FileName, Succeeded, !IO):
%
% Update the modification time for the given file,
% clobbering the contents of the file.
%
:- pred touch_file_datestamp(globals::in, io.text_output_stream::in,
file_name::in, maybe_succeeded::out, io::di, io::uo) is det.
%-----------------------------------------------------------------------------%
% If the argument is `did_not_succeed', set the exit status to 1.
%
:- pred maybe_set_exit_status(maybe_succeeded::in, io::di, io::uo) is det.
%-----------------------------------------------------------------------------%
%
% Java command-line tools utilities.
%
% Create a shell script with the same name as the given module to invoke
% Java with the appropriate options on the class of the same name.
%
:- pred create_java_shell_script(io.text_output_stream::in, globals::in,
module_name::in, maybe_succeeded::out, io::di, io::uo) is det.
% Return the standard Mercury libraries needed for a Java program.
% Return the empty list if --mercury-standard-library-directory
% is not set.
%
:- pred get_mercury_std_libs_for_java(globals::in, list(string)::out) is det.
% Given a list .class files, return the list of .class files that should be
% passed to `jar'. This is required because nested classes are in separate
% files which we don't know about, so we have to scan the directory to
% figure out which files were produced by `javac'.
%
:- pred list_class_files_for_jar(globals::in, list(string)::in, string::out,
list(string)::out, io::di, io::uo) is det.
% Given a `mmake' variable reference to a list of .class files, return an
% expression that generates the list of arguments for `jar' to reference
% those class files.
%
:- pred list_class_files_for_jar_mmake(globals::in, string::in, string::out)
is det.
% Get the value of the Java class path from the environment. (Normally
% it will be obtained from the CLASSPATH environment variable, but if
% that isn't present then the java.class.path variable may be used instead.
% This is used for the Java back-end, which doesn't support environment
% variables properly.)
%
:- pred get_env_classpath(string::out, io::di, io::uo) is det.
%-----------------------------------------------------------------------------%
:- pred create_launcher_shell_script(io.text_output_stream::in,
globals::in, module_name::in, string::in, maybe_succeeded::out,
io::di, io::uo) is det.
:- pred create_launcher_batch_file(io.text_output_stream::in,
globals::in, module_name::in, string::in, maybe_succeeded::out,
io::di, io::uo) is det.
%-----------------------------------------------------------------------------%
%-----------------------------------------------------------------------------%
:- implementation.
:- import_module libs.copy_util.
:- import_module libs.options.
:- import_module parse_tree.java_names.
:- import_module bool.
:- import_module dir.
:- import_module int.
:- import_module io.call_system.
:- import_module io.environment.
:- import_module io.file.
:- import_module maybe.
:- import_module require.
:- import_module set.
:- import_module string.
%-----------------------------------------------------------------------------%
copy_dot_tmp_to_base_file_return_succeeded(ProgressStream, Globals,
OutputFileName, Succeeded, !IO) :-
copy_dot_tmp_to_base_file_return_changed(ProgressStream, Globals,
OutputFileName, Result, !IO),
(
( Result = base_file_new_or_changed
; Result = base_file_unchanged
),
Succeeded = succeeded
;
Result = dot_tmp_copy_error,
Succeeded = did_not_succeed
).
copy_dot_tmp_to_base_file_report_any_error(ProgressStream, Globals,
FileKindStr, OutputFileName, Succeeded, !IO) :-
copy_dot_tmp_to_base_file_return_changed(ProgressStream, Globals,
OutputFileName, Result, !IO),
(
Result = dot_tmp_copy_error,
Succeeded = did_not_succeed,
string.format("problem updating %s files.", [s(FileKindStr)], Msg),
report_error(ProgressStream, Msg, !IO)
;
( Result = base_file_new_or_changed
; Result = base_file_unchanged
),
Succeeded = succeeded
).
copy_dot_tmp_to_base_file_return_changed(ProgressStream, Globals,
OutputFileName, Result, !IO) :-
globals.lookup_bool_option(Globals, verbose, Verbose),
maybe_write_string(ProgressStream, Verbose,
"% Updating interface:\n", !IO),
TmpOutputFileName = OutputFileName ++ ".tmp",
io.read_named_file_as_string(OutputFileName, OutputFileRes, !IO),
(
OutputFileRes = ok(OutputFileStr),
io.read_named_file_as_string(TmpOutputFileName, TmpOutputFileRes, !IO),
(
TmpOutputFileRes = ok(TmpOutputFileStr),
( if OutputFileStr = TmpOutputFileStr then
Result = base_file_unchanged,
string.format("%% `%s' has not changed.\n",
[s(OutputFileName)], NoChangeMsg),
maybe_write_string(ProgressStream, Verbose, NoChangeMsg, !IO),
io.file.remove_file(TmpOutputFileName, _, !IO)
else
copy_dot_tmp_to_base_file_create_file(Globals, ProgressStream,
"CHANGED", OutputFileName, TmpOutputFileName, Result, !IO)
)
;
TmpOutputFileRes = error(TmpOutputFileError),
io.error_message(TmpOutputFileError, TmpOutputFileErrorMsg),
Result = dot_tmp_copy_error,
% The error message is about TmpOutputFileName, but the
% message we print does not mention that file name.
io.format(ProgressStream, "Error creating `%s': %s\n",
[s(OutputFileName), s(TmpOutputFileErrorMsg)], !IO)
)
;
OutputFileRes = error(_),
copy_dot_tmp_to_base_file_create_file(Globals, ProgressStream,
"been CREATED", OutputFileName, TmpOutputFileName, Result, !IO)
).
%-----------------------------------------------------------------------------%
:- pred copy_dot_tmp_to_base_file_create_file(globals::in,
io.text_output_stream::in, string::in, string::in, string::in,
dot_tmp_copy_result::out, io::di, io::uo) is det.
copy_dot_tmp_to_base_file_create_file(Globals, ProgressStream,
ChangedStr, OutputFileName, TmpOutputFileName, Result, !IO) :-
globals.lookup_bool_option(Globals, verbose, Verbose),
string.format("%% `%s' has %s.\n", [s(OutputFileName), s(ChangedStr)],
ChangedMsg),
maybe_write_string(ProgressStream, Verbose, ChangedMsg, !IO),
copy_file_to_file_name(Globals, ProgressStream, TmpOutputFileName,
OutputFileName, MoveRes, !IO),
(
MoveRes = succeeded,
Result = base_file_new_or_changed
;
MoveRes = did_not_succeed,
Result = dot_tmp_copy_error
% copy_file_to_file_name/7 writes an error message to ProgressStream
% if the copy fails.
% XXX FILE COPY: we used to generate the following error message
% here, but I suspect it wasn't really possible to trigger it.
%MoveRes = error(MoveError),
%io.format(ProgressStream, "Error creating `%s': %s\n",
% [s(OutputFileName), s(io.error_message(MoveError))], !IO)
),
io.file.remove_file(TmpOutputFileName, _, !IO).
%-----------------------------------------------------------------------------%
maybe_make_symlink(Globals, LinkTarget, LinkName, Result, !IO) :-
globals.lookup_bool_option(Globals, use_symlinks, UseSymLinks),
(
UseSymLinks = yes,
definitely_make_symlink(LinkTarget, LinkName, Result, !IO)
;
UseSymLinks = no,
Result = did_not_succeed
).
definitely_make_symlink(LinkTarget, LinkName, Result, !IO) :-
io.file.remove_file_recursively(LinkName, _, !IO),
io.file.make_symlink(LinkTarget, LinkName, LinkResult, !IO),
Result = ( if LinkResult = ok then succeeded else did_not_succeed ).
make_symlink_or_copy_file(Globals, ProgressStream,
SourceFileName, DestinationFileName, Succeeded, !IO) :-
globals.lookup_bool_option(Globals, use_symlinks, UseSymLinks),
globals.lookup_bool_option(Globals, verbose_commands, PrintCommand),
(
UseSymLinks = yes,
LinkOrCopy = "linking",
(
PrintCommand = yes,
io.format(ProgressStream, "%% Linking file `%s' -> `%s'\n",
[s(SourceFileName), s(DestinationFileName)], !IO),
io.flush_output(ProgressStream, !IO)
;
PrintCommand = no
),
io.file.make_symlink(SourceFileName, DestinationFileName, Result, !IO),
(
Result = ok,
Succeeded = succeeded
;
Result = error(Error),
Succeeded = did_not_succeed,
io.progname_base("mercury_compile", ProgName, !IO),
io.error_message(Error, ErrorMsg),
io.format(ProgressStream, "%s: error %s `%s' to `%s', %s\n",
[s(ProgName), s(LinkOrCopy), s(SourceFileName),
s(DestinationFileName), s(ErrorMsg)], !IO),
io.flush_output(ProgressStream, !IO)
)
;
UseSymLinks = no,
%LinkOrCopy = "copying",
(
PrintCommand = yes,
io.format(ProgressStream, "%% Copying file `%s' -> `%s'\n",
[s(SourceFileName), s(DestinationFileName)], !IO),
io.flush_output(ProgressStream, !IO)
;
PrintCommand = no
),
% XXX FILE COPY
% copy_file_to_file_name/7 will write an error message
% to ProgressStream itself if the file copy fails. We used to generate
% an error similar to the symlink case above here.
copy_file_to_file_name(Globals, ProgressStream, SourceFileName,
DestinationFileName, Succeeded, !IO)
).
%-----------------------------------------------------------------------------%
touch_module_ext_datestamp(Globals, ProgressStream, ModuleName, Ext,
Succeeded, !IO) :-
% XXX LEGACY
module_name_to_file_name_create_dirs(Globals, $pred, Ext,
ModuleName, FileName, _FileNameProposed, !IO),
touch_file_datestamp(Globals, ProgressStream, FileName, Succeeded, !IO).
touch_file_datestamp(Globals, ProgressStream, FileName, Succeeded, !IO) :-
globals.lookup_bool_option(Globals, verbose, Verbose),
maybe_write_string(ProgressStream, Verbose,
"% Touching `" ++ FileName ++ "'... ", !IO),
maybe_flush_output(ProgressStream, Verbose, !IO),
io.open_output(FileName, Result, !IO),
(
Result = ok(FileStream),
% This write does the "touching", i.e. the updating of the file's
% time of last modification.
io.write_string(FileStream, "\n", !IO),
io.close_output(FileStream, !IO),
maybe_write_string(ProgressStream, Verbose, " done.\n", !IO),
Succeeded = succeeded
;
Result = error(IOError),
io.error_message(IOError, IOErrorMessage),
io.format(ProgressStream, "\nError opening `%s' for output: %s.\n",
[s(FileName), s(IOErrorMessage)], !IO),
Succeeded = did_not_succeed
).
%-----------------------------------------------------------------------------%
maybe_set_exit_status(succeeded, !IO).
maybe_set_exit_status(did_not_succeed, !IO) :-
io.set_exit_status(1, !IO).
%-----------------------------------------------------------------------------%
%
% Java command-line utilities.
%
create_java_shell_script(ProgressStream, Globals, MainModuleName,
Succeeded, !IO) :-
% XXX LEGACY
module_name_to_file_name(Globals, $pred, ext_cur_gs(ext_cur_gs_lib_jar),
MainModuleName, JarFileName, _JarFileNameProposed),
get_target_env_type(Globals, TargetEnvType),
(
( TargetEnvType = env_type_posix
; TargetEnvType = env_type_cygwin
),
io.environment.get_environment_var("MERCURY_STAGE2_LAUNCHER_BASE",
MaybeStage2Base, !IO),
construct_java_shell_script(Globals, MaybeStage2Base,
MainModuleName, JarFileName, ContentStr),
create_launcher_shell_script(ProgressStream, Globals, MainModuleName,
ContentStr, Succeeded, !IO)
;
TargetEnvType = env_type_msys,
construct_java_msys_shell_script(Globals, MainModuleName, JarFileName,
ContentStr),
create_launcher_shell_script(ProgressStream, Globals, MainModuleName,
ContentStr, Succeeded, !IO)
;
% XXX should create a .ps1 file on PowerShell.
( TargetEnvType = env_type_win_cmd
; TargetEnvType = env_type_powershell
),
construct_java_batch_file(Globals, MainModuleName, JarFileName,
ContentStr),
create_launcher_batch_file(ProgressStream, Globals, MainModuleName,
ContentStr, Succeeded, !IO)
).
:- pred construct_java_shell_script(globals::in, maybe(string)::in,
module_name::in, file_name::in, string::out) is det.
construct_java_shell_script(Globals, MaybeStage2Base,
MainModuleName, JarFileName, ContentStr) :-
(
MaybeStage2Base = no,
get_mercury_std_libs_for_java(Globals, MercuryStdLibs)
;
MaybeStage2Base = yes(Stage2Base),
MercuryStdLibs = [
Stage2Base / "library/mer_rt.jar",
Stage2Base / "library/mer_std.jar"
]
),
globals.lookup_accumulating_option(Globals, java_classpath,
UserClasspath),
% We prepend the .class files' directory and the current CLASSPATH.
Java_Incl_Dirs = ["\"$DIR/" ++ JarFileName ++ "\""] ++
MercuryStdLibs ++ ["$CLASSPATH" | UserClasspath],
ClassPath = string.join_list("${SEP}", Java_Incl_Dirs),
globals.lookup_accumulating_option(Globals, java_runtime_flags,
RuntimeFlags),
RuntimeOpts0 = string.join_list(" ", RuntimeFlags),
RuntimeOpts = escape_single_quotes_for_shell_script(RuntimeOpts0),
globals.lookup_string_option(Globals, java_interpreter, Java),
mangle_sym_name_for_java(MainModuleName, module_qual, ".", ClassName),
ContentStr = string.append_list([
"#!/bin/sh\n",
"DIR=${0%/*}\n",
"DIR=$( cd \"${DIR}\" && pwd -P )\n",
"case $WINDIR in\n",
" '') SEP=':' ;;\n",
" *) SEP=';' ;;\n",
"esac\n",
"CLASSPATH=", ClassPath, "\n",
"export CLASSPATH\n",
"MERCURY_JAVA=${MERCURY_JAVA:-'", Java, "'}\n",
"MERCURY_JAVA_OPTIONS=${MERCURY_JAVA_OPTIONS:-'", RuntimeOpts, "'}\n",
"exec \"$MERCURY_JAVA\" $MERCURY_JAVA_OPTIONS jmercury.", ClassName,
" \"$@\"\n"
]).
% For the MSYS version of the Java launcher script, there are a few
% differences:
%
% 1. The value of the CLASSPATH environment variable we construct for the
% Java interpreter must contain Windows style paths.
%
% 2. We use forward slashes as directory separators rather than back
% slashes since the latter require escaping inside the shell script.
%
% 3. The path separator character, ';', in the value of CLASSPATH must be
% escaped because it is a statement separator in sh.
%
% 4. The path of the Java interpreter must be a Unix style path as it will
% be invoked directly from the MSYS shell.
%
% XXX TODO: handle MERCURY_STAGE2_LAUNCHER_BASE for this case.
%
:- pred construct_java_msys_shell_script(globals::in, module_name::in,
file_name::in, string::out) is det.
construct_java_msys_shell_script(Globals, MainModuleName, JarFileName,
ContentStr) :-
get_mercury_std_libs_for_java(Globals, MercuryStdLibs),
globals.lookup_accumulating_option(Globals, java_classpath,
UserClasspath),
% We prepend the .class files' directory and the current CLASSPATH.
Java_Incl_Dirs0 = ["\"$DIR/" ++ JarFileName ++ "\""] ++
MercuryStdLibs ++ ["$CLASSPATH" | UserClasspath],
Java_Incl_Dirs = list.map(func(S) = string.replace_all(S, "\\", "/"),
Java_Incl_Dirs0),
ClassPath = string.join_list("\\;", Java_Incl_Dirs),
globals.lookup_accumulating_option(Globals, java_runtime_flags,
RuntimeFlags),
RuntimeOpts0 = string.join_list(" ", RuntimeFlags),
RuntimeOpts = escape_single_quotes_for_shell_script(RuntimeOpts0),
globals.lookup_string_option(Globals, java_interpreter, Java),
mangle_sym_name_for_java(MainModuleName, module_qual, ".", ClassName),
ContentStr = string.append_list([
"#!/bin/sh\n",
"DIR=${0%/*}\n",
"DIR=$( cd \"${DIR}\" && pwd -W )\n",
"CLASSPATH=", ClassPath, "\n",
"export CLASSPATH\n",
"MERCURY_JAVA=${MERCURY_JAVA:-'", Java, "'}\n",
"MERCURY_JAVA_OPTIONS=${MERCURY_JAVA_OPTIONS:-'", RuntimeOpts, "'}\n",
"exec \"$MERCURY_JAVA\" $MERCURY_JAVA_OPTIONS jmercury.", ClassName,
" \"$@\"\n"
]).
:- func escape_single_quotes_for_shell_script(string) = string.
escape_single_quotes_for_shell_script(S) =
( if string.contains_char(S, '\'') then
string.replace_all(S, "'", "'\\''")
else
S
).
:- pred construct_java_batch_file(globals::in, module_name::in, file_name::in,
string::out) is det.
construct_java_batch_file(Globals, MainModuleName, JarFileName, ContentStr) :-
get_mercury_std_libs_for_java(Globals, MercuryStdLibs),
globals.lookup_accumulating_option(Globals, java_classpath,
UserClasspath),
% We prepend the .class files' directory and the current CLASSPATH.
Java_Incl_Dirs = ["%DIR%\\" ++ JarFileName] ++ MercuryStdLibs ++
["%CLASSPATH%" | UserClasspath],
ClassPath = string.join_list(";", Java_Incl_Dirs),
globals.lookup_accumulating_option(Globals, java_runtime_flags,
RuntimeFlags),
RuntimeOpts = string.join_list(" ", RuntimeFlags),
globals.lookup_string_option(Globals, java_interpreter, Java),
mangle_sym_name_for_java(MainModuleName, module_qual, ".", ClassName),
ContentStr = string.append_list([
"@echo off\n",
"rem Automatically generated by the Mercury compiler.\n",
"setlocal enableextensions\n",
"set DIR=%~dp0\n",
"set CLASSPATH=", ClassPath, "\n",
"if not defined MERCURY_JAVA_OPTIONS set MERCURY_JAVA_OPTIONS=",
RuntimeOpts, "\n",
Java, " %MERCURY_JAVA_OPTIONS% jmercury.", ClassName, " %*\n"
]).
get_mercury_std_libs_for_java(Globals, !:StdLibs) :-
% NOTE: changes here may require changes to get_mercury_std_libs.
!:StdLibs = [],
globals.lookup_maybe_string_option(Globals,
mercury_standard_library_directory, MaybeStdLibDir),
(
MaybeStdLibDir = yes(StdLibDir),
globals.get_grade_dir(Globals, GradeDir),
% Source-to-source debugging libraries.
globals.lookup_bool_option(Globals, link_ssdb_libs,
SourceDebug),
(
SourceDebug = yes,
list.cons(StdLibDir/"lib"/GradeDir/"mer_browser.jar", !StdLibs),
list.cons(StdLibDir/"lib"/GradeDir/"mer_mdbcomp.jar", !StdLibs),
list.cons(StdLibDir/"lib"/GradeDir/"mer_ssdb.jar", !StdLibs)
;
SourceDebug = no
),
list.cons(StdLibDir/"lib"/GradeDir/"mer_std.jar", !StdLibs),
list.cons(StdLibDir/"lib"/GradeDir/"mer_rt.jar", !StdLibs)
;
MaybeStdLibDir = no
).
list_class_files_for_jar(Globals, MainClassFiles, ClassSubDir,
ListClassFiles, !IO) :-
% XXX LEGACY
get_java_dir_path(Globals, ext_cur_ngs_gs_java_class,
ClassSubDirPath, _ClassSubDirPathProposed),
ClassSubDir = dir.relative_path_name_from_components(ClassSubDirPath),
list.filter_map(make_nested_class_prefix, MainClassFiles,
NestedClassPrefixes),
NestedClassPrefixesSet = set.list_to_set(NestedClassPrefixes),
SearchDir = ClassSubDir / "jmercury",
SubDir = enter_subdirs(follow_symlinks),
FoldParams = fold_params(SubDir, on_error_keep_going),
% Unfortunately, dir.general_foldl2 is not *quite* general enough
% that we could tell it to not even try to open any file or directory
% that does not start with a prefix in NestedClassPrefixesSet.
dir.general_foldl2(FoldParams,
accumulate_nested_class_files(NestedClassPrefixesSet),
SearchDir, [], NestedClassFiles, Errors, !IO),
list.filter(file_error_is_relevant(NestedClassPrefixesSet),
Errors, RelevantErrors),
(
RelevantErrors = [],
AllClassFiles0 = MainClassFiles ++ NestedClassFiles,
% Remove the `Mercury/classes' prefix if present.
( if ClassSubDir = dir.this_directory then
AllClassFiles = AllClassFiles0
else
ClassSubDirSep = ClassSubDir / "",
AllClassFiles = list.map(
string.remove_prefix_if_present(ClassSubDirSep),
AllClassFiles0)
),
list.sort(AllClassFiles, ListClassFiles)
;
RelevantErrors = [file_error(_, _, Error) | _],
unexpected($pred, io.error_message(Error))
).
list_class_files_for_jar_mmake(Globals, ClassFiles, ListClassFiles) :-
% XXX LEGACY
get_java_dir_path(Globals, ext_cur_ngs_gs_java_class,
ClassSubDirPath, _ClassSubDirPathProposed),
(
ClassSubDirPath = [],
ListClassFiles = ClassFiles
;
ClassSubDirPath = [_ | _],
ClassSubDir = dir.relative_path_name_from_components(ClassSubDirPath),
% Here we use the `-C' option of jar to change directory during
% execution, then use sed to strip away the Mercury/classes/ prefix
% to the class files.
% Otherwise, the class files would be stored as
% Mercury/classes/*.class
% within the jar file, which is not what we want.
% XXX It would be nice to avoid this dependency on sed.
ListClassFiles = "-C " ++ ClassSubDir ++ " \\\n" ++
"\t\t`echo "" " ++ ClassFiles ++ """" ++
" | sed 's| '" ++ ClassSubDir ++ "/| |'`"
).
:- pred make_nested_class_prefix(string::in, string::out) is semidet.
make_nested_class_prefix(ClassFileName, ClassPrefix) :-
% Nested class files are named "Class$Nested_1$Nested_2.class".
string.remove_suffix(ClassFileName, ".class", BaseName),
ClassPrefix = BaseName ++ "$".
:- pred accumulate_nested_class_files(set(string)::in, string::in, string::in,
io.file_type::in, bool::out, list(string)::in, list(string)::out,
io::di, io::uo) is det.
accumulate_nested_class_files(NestedClassPrefixes, DirName, BaseName,
FileType, Continue, !Acc, !IO) :-
(
% These file types may be .class files.
( FileType = regular_file
; FileType = symbolic_link
),
IsNestedCF =
file_is_nested_class_file(NestedClassPrefixes, DirName, BaseName),
(
IsNestedCF = yes,
!:Acc = [DirName / BaseName | !.Acc]
;
IsNestedCF = no
)
;
% These file types cannot be .class files.
( FileType = directory
; FileType = named_pipe
; FileType = socket
; FileType = character_device
; FileType = block_device
; FileType = message_queue
; FileType = semaphore
; FileType = shared_memory
; FileType = unknown
)
),
Continue = yes.
:- func file_is_nested_class_file(set(string), string, string) = bool.
file_is_nested_class_file(NestedClassPrefixes, DirName, BaseName)
= IsNestedCF :-
( if
string.sub_string_search(BaseName, "$", Dollar),
BaseNameToDollar = string.left(BaseName, Dollar + 1),
set.contains(NestedClassPrefixes, DirName / BaseNameToDollar)
then
IsNestedCF = yes
else
IsNestedCF = no
).
:- pred file_error_is_relevant(set(string)::in, file_error::in)
is semidet.
file_error_is_relevant(NestedClassPrefixes, FileError) :-
FileError = file_error(PathName, _Op, _IOError),
( if split_name(PathName, DirName, BaseName) then
file_is_nested_class_file(NestedClassPrefixes, DirName, BaseName) = yes
else
% If we cannot read the top level SearchDir, that error is relevant.
true
).
%-----------------------------------------------------------------------------%
get_env_classpath(Classpath, !IO) :-
io.environment.get_environment_var("CLASSPATH", MaybeCP, !IO),
(
MaybeCP = yes(Classpath)
;
MaybeCP = no,
io.environment.get_environment_var("java.class.path", MaybeJCP, !IO),
(
MaybeJCP = yes(Classpath)
;
MaybeJCP = no,
Classpath = ""
)
).
%-----------------------------------------------------------------------------%
create_launcher_shell_script(ProgressStream, Globals, MainModuleName,
ContentStr, Succeeded, !IO) :-
Ext = ext_cur_gas(ext_cur_gas_exec_noext),
% XXX LEGACY
module_name_to_file_name_create_dirs(Globals, $pred, Ext,
MainModuleName, LauncherFileName, _LauncherFileNameProposed, !IO),
globals.lookup_bool_option(Globals, verbose, Verbose),
maybe_write_string(ProgressStream, Verbose,
"% Generating shell script `" ++ LauncherFileName ++ "'...\n", !IO),
% Remove symlink in the way, if any.
io.file.remove_file(LauncherFileName, _, !IO),
io.open_output(LauncherFileName, OpenResult, !IO),
(
OpenResult = ok(Stream),
io.write_string(Stream, ContentStr, !IO),
io.close_output(Stream, !IO),
io.call_system.call_system("chmod a+x " ++ LauncherFileName,
ChmodResult, !IO),
(
ChmodResult = ok(Status),
( if Status = 0 then
Succeeded = succeeded,
maybe_write_string(ProgressStream, Verbose, "% done.\n", !IO)
else
unexpected($pred, "chmod exit status != 0"),
Succeeded = did_not_succeed
)
;
ChmodResult = error(Message),
unexpected($pred, io.error_message(Message)),
Succeeded = did_not_succeed
)
;
OpenResult = error(Message),
unexpected($pred, io.error_message(Message)),
Succeeded = did_not_succeed
).
%-----------------------------------------------------------------------------%
create_launcher_batch_file(ProgressStream, Globals, MainModuleName,
ContentStr, Succeeded, !IO) :-
% XXX LEGACY
module_name_to_file_name_create_dirs(Globals, $pred,
ext_cur_gas(ext_cur_gas_exec_bat), MainModuleName,
FileName, _FileNameProposed, !IO),
globals.lookup_bool_option(Globals, verbose, Verbose),
maybe_write_string(ProgressStream, Verbose,
"% Generating batch file `" ++ FileName ++ "'...\n", !IO),
% Remove an existing batch file of the same name, if any.
io.file.remove_file(FileName, _, !IO),
io.open_output(FileName, OpenResult, !IO),
(
OpenResult = ok(Stream),
io.write_string(Stream, ContentStr, !IO),
io.close_output(Stream, !IO),
Succeeded = succeeded
;
OpenResult = error(Message),
unexpected($pred, io.error_message(Message)),
Succeeded = did_not_succeed
).
%-----------------------------------------------------------------------------%
:- end_module parse_tree.module_cmds.
%-----------------------------------------------------------------------------%