1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
|
needs "methods.m2"
Program = new Type of HashTable
ProgramRun = new Type of HashTable
programPaths = new MutableHashTable
fixPath = programPath -> (
-- escape any unescaped spaces or parentheses
programPath = replace(///(?<!\\)([ ()])///, ///\\\1///, programPath);
-- we expect a trailing slash in the path, but the paths given in the
-- PATH environment variable likely will not have one, so we add one
-- if needed
if last programPath != "/" then programPath | "/"
else programPath
)
-- returns (found, thisVersion)
-- found is an integer:
foundProgram := 0
didNotFindProgram := 1
foundProgramButOldVersion := 2
foundProgramButBadVersion := 3
-- thisVersion is a string containing the version number found, or null
-- if MinimumVersion option is not given or the version number can't be
-- be determined.
checkProgramPath = (cmds, pathToTry, prefix, opts) -> (
verboseLog := if opts.Verbose then printerr else identity;
-- unescape spaces/parentheses and resolve HOME for fileExists and fileMode
unescapedPathToTry := replace(///^\$\{?HOME\}?///, getenv "HOME",
replace(///\\([ ()])///, ///\1///, pathToTry));
found := if all(apply(cmds, cmd -> addPrefix(cmd, prefix)), cmd -> (
exe := unescapedPathToTry | first separate(" ", cmd);
if not fileExists exe then (
verboseLog(exe, " does not exist"); false) else
-- check executable bit
if fileMode exe & 0o111 == 0 then (
verboseLog(exe, " exists but is not executable"); false) else (
verboseLog(exe, " exists and is executable");
verboseLog("running ", format(pathToTry | cmd), ":");
ret := run(pathToTry | cmd |
if opts.Verbose then "" else " > /dev/null 2>&1" );
verboseLog("return value: " | ret);
ret == 0))) then foundProgram else didNotFindProgram;
thisVersion := null;
if found == foundProgram and opts.MinimumVersion =!= null then (
thisVersion = replace("(^\\s+)|(\\s+$)", "", get("!" | pathToTry |
addPrefix(opts.MinimumVersion_1, prefix)));
if not match(///^\d[\-+\.:\~\da-zA-Z]*$///, thisVersion) then (
verboseLog("found version ", format thisVersion,
" but this does not appear to be a valid version number");
found = foundProgramButBadVersion;
thisVersion = null;
) else if thisVersion >= opts.MinimumVersion_0 then
verboseLog("found version ", thisVersion, " >= ",
opts.MinimumVersion_0)
else (
verboseLog("found, but version ", thisVersion, " < ",
opts.MinimumVersion_0);
found = foundProgramButOldVersion;
)
);
(found, thisVersion)
)
addPrefix = (cmd, prefix) ->
if match(prefix_0, first separate(" ", cmd)) then prefix_1 | cmd else cmd
findProgram = method(TypicalValue => Program,
Options => {
RaiseError => true,
Verbose => false,
Prefix => {},
AdditionalPaths => {},
MinimumVersion => null
})
findProgram(String, String) := opts -> (name, cmd) ->
findProgram(name, {cmd}, opts)
findProgram(String, List) := opts -> (name, cmds) -> (
if not (instance(opts.Prefix, List) and
all(opts.Prefix, x -> instance(x, Sequence)) and
all(opts.Prefix, x -> class \ x === (String, String))) then
error "expected Prefix to be a list of sequences of two strings";
if not (instance(opts.AdditionalPaths, List) and
all(opts.AdditionalPaths, x -> instance(x, String))) then
error "expected AdditionalPaths to be a list of strings";
if opts.MinimumVersion =!= null and not(
instance(opts.MinimumVersion, Sequence) and
class \ opts.MinimumVersion === (String, String)) then
error "expected MinimumVersion to be a sequence of two strings";
pathsToTry := fixPath \ join(
-- try user-configured path first
if programPaths#?name then {programPaths#name} else {},
-- now try M2-installed path
{prefixDirectory | currentLayout#"programs"},
-- any additional paths specified by the caller
opts.AdditionalPaths,
-- try PATH
if getenv "PATH" == "" then {} else apply(separate(":", getenv "PATH"),
dir -> if dir == "" then "." else dir),
-- try directory containing M2-binary
{bindir});
prefixes := {(".*", "")} | opts.Prefix;
errorCode := didNotFindProgram;
versionFound := "0.0";
for pathToTry in pathsToTry do for prefix in prefixes do (
(found, thisVersion) := checkProgramPath(cmds, pathToTry, prefix, opts);
if found == foundProgram then return new Program from {
"name" => name,
"path" => pathToTry,
"prefix" => prefix } | if opts.MinimumVersion =!= null then {
"version" => thisVersion} else {} else
if found != didNotFindProgram then (
errorCode = found;
if found == foundProgramButOldVersion and
thisVersion > versionFound then versionFound = thisVersion
)
);
if opts.RaiseError then error(
if errorCode == didNotFindProgram then "could not find " | name else
if errorCode == foundProgramButOldVersion then "found " | name |
", but version (" | versionFound | ") is too low" else
if errorCode == foundProgramButBadVersion then "found " | name |
", but could not determine version" else "unknown error");
)
runProgram = method(TypicalValue => ProgramRun,
Options => {
RaiseError => true,
KeepFiles => false,
Verbose => false,
RunDirectory => null
})
runProgram(Program, String) := opts -> (program, args) ->
runProgram(program, program#"name", args, opts)
runProgram(Program, String, String) := opts -> (program, name, args) -> (
tmpFile := temporaryFileName();
outFile := tmpFile | ".out";
errFile := tmpFile | ".err";
cmd := if opts.RunDirectory =!= null then (
if not isDirectory opts.RunDirectory then
makeDirectory opts.RunDirectory;
"cd " | opts.RunDirectory | " && " ) else "";
cmd = cmd | program#"path" | addPrefix(name, program#"prefix") | " " | args;
if match("\\|", cmd) then cmd = "{ " | cmd | ";}";
returnValue := run (cmd | " > " | outFile | " 2> " | errFile);
message := "running: " | cmd | "\n";
output := get outFile;
if output != "" then message = message | output;
err := get errFile;
if err != "" then message = message | err;
if opts.Verbose then print(message);
result := {
"command" => cmd,
"output" => output,
"error" => err,
"return value" => returnValue};
if opts.KeepFiles then result = result | {
"output file" => outFile,
"error file" => errFile}
else (
removeFile outFile;
removeFile errFile;
);
if opts.RaiseError and returnValue != 0 then error(
program#"name" | " returned an error" |
if opts.Verbose then "" else "\n" | message);
new ProgramRun from result
)
net Program := toString Program := program -> program#"name"
html Program := html @@ toString
net ProgramRun := pr -> net pr#"return value"
|