mirror of
https://github.com/WolframResearch/WolframLanguageForJupyter.git
synced 2025-04-17 12:16:06 +00:00
464 lines
16 KiB
Plaintext
Executable File
464 lines
16 KiB
Plaintext
Executable File
#!/usr/bin/env wolframscript
|
|
|
|
Begin["WolframLanguageForJupyter`Private`"];
|
|
|
|
notfound = "configure-jupyter.wls: Jupyter installation on Environment[\"PATH\"] not found.";
|
|
isdir = "configure-jupyter.wls: Provided Jupyter binary path is a directory. Please provide the path to the Jupyter binary."
|
|
nobin = "configure-jupyter.wls: Provided Jupyter binary path does not exist.";
|
|
isdirMath = "configure-jupyter.wls: Provided Wolfram Engine binary path is a directory. Please provide the path to the Wolfram Engine binary."
|
|
nobinMath = "configure-jupyter.wls: Provided Wolfram Engine binary path does not exist.";
|
|
notadded = "configure-jupyter.wls: An error has occurred. The desired Wolfram Engine is not in \"jupyter kernelspec list.\"";
|
|
notremoved = "configure-jupyter.wls: An error has occurred: Wolfram Engine(s) still in \"jupyter kernelspec list.\"";
|
|
addconflict = "configure-jupyter.wls: An error has occurred. A Wolfram Engine with the same $VersionNumber of the target Wolfram Engine is in \"jupyter kernelspec list.\" Attempting to overwrite ...";
|
|
(* removeconflict = "configure-jupyter.wls: An error has occurred. The Wolfram Engine(s) to be removed is/are not in \"jupyter kernelspec list.\""; *)
|
|
removeconflict = "";
|
|
nopaclet = "configure-jupyter.wls: WolframLanguageForJupyter paclet source not detected. Are you running the script in the root project directory?";
|
|
nolink = "configure-jupyter.wls: Communication with provided Wolfram Engine binary could not be established.";
|
|
|
|
(*
|
|
Dictionary:
|
|
mathBin/mathBinSession = WolframKernel binary
|
|
kernelspec = Kernel Specification; term used by Jupyter
|
|
notProvidedQ = was a Wolfram Engine Binary explicitly specified?
|
|
*)
|
|
|
|
(* START: Helper symbols *)
|
|
|
|
projectHome = If[StringQ[$InputFileName] && $InputFileName != "", DirectoryName[$InputFileName], Directory[]];
|
|
|
|
(* establishes link with Wolfram Engine at mathBin and evaluates $Version/$VersionNumber *)
|
|
(* returns string form *)
|
|
getVersionFromKernel[mathBin_String] :=
|
|
Module[{link, res},
|
|
link =
|
|
LinkLaunch[
|
|
StringJoin[
|
|
{
|
|
"\"",
|
|
mathBin,
|
|
"\" -wstp"
|
|
}
|
|
]
|
|
];
|
|
If[FailureQ[link],
|
|
Return[$Failed];
|
|
];
|
|
(* bleed link *)
|
|
While[LinkReadyQ[link, 0.5], LinkRead[link];];
|
|
LinkWrite[link, Unevaluated[$VersionNumber]];
|
|
res = StringTrim[ToString[LinkRead[link]], "ReturnPacket[" | "]"];
|
|
LinkClose[link];
|
|
If[!StringContainsQ[res, "[" | "]"],
|
|
Return[res];,
|
|
Return[$Failed];
|
|
];
|
|
];
|
|
|
|
(* determine display name for Jupyter installation from Wolfram Engine $Version/$VersionNumber *)
|
|
(* returns {Kernel ID, Display Name} *)
|
|
getNames[mathBin_String, notProvidedQ_?BooleanQ] :=
|
|
Module[{version, installDir, (* names, hashedKernelUUID *) versionStr},
|
|
(* if Wolfram Engine binary not provided, just evaluate $Version in the current session *)
|
|
(* otherwise, use MathLink to obtain $Version *)
|
|
If[
|
|
notProvidedQ,
|
|
version = ToString[$VersionNumber];
|
|
installDir = $InstallationDirectory;
|
|
,
|
|
version = Quiet[getVersionFromKernel[mathBin]];
|
|
If[
|
|
FailureQ[version],
|
|
Return[$Failed];
|
|
];
|
|
installDir = mathBin;
|
|
];
|
|
|
|
(*
|
|
|
|
hashedKernelUUID = StringJoin["wl-script-", Hash[installDir, "SHA", "HexString"]];
|
|
|
|
names = StringCases[version, name___ ~~ " for " ~~ ("Mac" | "Microsoft" | "Windows" | "Linux") -> name];
|
|
Return[
|
|
If[Length[names] > 0,
|
|
{
|
|
ToLowerCase[StringJoin[
|
|
"WolframLanguage-script-",
|
|
StringReplace[First[names], Whitespace -> "-"]
|
|
]],
|
|
StringJoin[
|
|
"Wolfram Language (",
|
|
Capitalize[
|
|
First[names],
|
|
"AllWords"
|
|
],
|
|
") | Script Install"
|
|
]
|
|
}
|
|
,
|
|
{hashedKernelUUID, "Wolfram Language | Script Install"}
|
|
]
|
|
];
|
|
|
|
*)
|
|
|
|
versionStr = StringTrim[version, "."];
|
|
Return[
|
|
{
|
|
(* Kernel ID *)
|
|
StringJoin["wolframlanguage", versionStr],
|
|
(* Display Name *)
|
|
StringJoin["Wolfram Language ", versionStr]
|
|
}
|
|
];
|
|
];
|
|
|
|
(* determine symbols related to finding Wolfram Engine and Jupyter installations *)
|
|
(* mathBinSession: WolframKernel location for the current session *)
|
|
(* fileExt: file extension for executables *)
|
|
(* pathSeperator: delimiter for directories on PATH *)
|
|
defineGlobalVars[] :=
|
|
Switch[
|
|
$OperatingSystem,
|
|
"Windows",
|
|
mathBinSession = FileNameJoin[{$InstallationDirectory, "wolfram.exe"}];
|
|
fileExt = ".exe";
|
|
pathSeperator = ";";,
|
|
"MacOSX",
|
|
mathBinSession = FileNameJoin[{$InstallationDirectory, "MacOS", "WolframKernel"}];
|
|
fileExt = "";
|
|
pathSeperator = ":";,
|
|
"Unix",
|
|
mathBinSession = FileNameJoin[{$InstallationDirectory, "Executables", "WolframKernel"}];
|
|
fileExt = "";
|
|
pathSeperator = ":";
|
|
];
|
|
|
|
mathBinSession := (defineGlobalVars[]; mathBinSession);
|
|
fileExt := (defineGlobalVars[]; fileExt);
|
|
pathSeperator := (defineGlobalVars[]; pathSeperator);
|
|
|
|
(* a list of directories in PATH *)
|
|
splitPath := StringSplit[Environment["PATH"], pathSeperator];
|
|
|
|
(* restore PATH, if due to a bug, it becomes essentially empty; this is relevant to finding the Jupyter installation *)
|
|
(* returns above *)
|
|
attemptPathRegeneration[] := If[
|
|
$OperatingSystem === "MacOSX" && FileType["~/.profile"] === File,
|
|
Print["install.wls: Warning: Regenerating PATH ..."];
|
|
SetEnvironment[
|
|
"PATH" -> StringTrim[
|
|
RunProcess[
|
|
$SystemShell,
|
|
"StandardOutput",
|
|
StringJoin[Import["~/.profile", "String"], "\necho $PATH"],
|
|
ProcessEnvironment -> {}
|
|
],
|
|
"\n"
|
|
]
|
|
];
|
|
];
|
|
|
|
(* find Jupyter installation path *)
|
|
(* returns kernel IDs in Jupyter *)
|
|
findJupyterPath[] :=
|
|
SelectFirst[
|
|
splitPath,
|
|
(* check every directory in PATH to see if a Jupyter binary is a member *)
|
|
(FileType[FileNameJoin[{#1, StringJoin["jupyter", fileExt]}]] === File)&
|
|
];
|
|
|
|
(* get information about installed kernels in Jupyter *)
|
|
(* returns kernel IDs in Jupyter *)
|
|
getKernels[jupyterPath_String, processEnvironment_] :=
|
|
Module[{json, kernelspecAssoc},
|
|
(* obtain information about "jupyter kernelspec list" in JSON *)
|
|
json = Quiet[ImportString[RunProcess[{jupyterPath, "kernelspec", "list", "--json"}, "StandardOutput", ProcessEnvironment -> processEnvironment], "JSON"]];
|
|
(* transform that JSON information into an Association *)
|
|
kernelspecAssoc =
|
|
If[
|
|
FailureQ[json],
|
|
Association[],
|
|
Replace[
|
|
json,
|
|
part_List /; AllTrue[part, Head[#1] === Rule &] -> Association @ part,
|
|
{0, Infinity}
|
|
]
|
|
];
|
|
Return[
|
|
(* if the above process worked, just return the kernel IDs of all the kernelspecs *)
|
|
(* otherwise, return an empty list *)
|
|
If[
|
|
KeyExistsQ[kernelspecAssoc, "kernelspecs"],
|
|
Keys[kernelspecAssoc["kernelspecs"]],
|
|
{}
|
|
]
|
|
];
|
|
];
|
|
|
|
|
|
(* END: Helper symbols *)
|
|
|
|
(* main install command *)
|
|
(* specs: options \"WolframEngineBinary\" and \"JupyterInstallation\" in an Association, when provided *)
|
|
(* removeQ: remove a Jupyter installation or not *)
|
|
(* removeAllQ: clear all Jupyter installations or not *)
|
|
(* removeQ first, removeAllQ second: "add" is False, False; "remove" is True, False, and "clear" is True, True *)
|
|
configureJupyter[specs_Association, removeQ_?BooleanQ, removeAllQ_?BooleanQ] :=
|
|
Module[
|
|
{
|
|
kernelScript,
|
|
retrievedNames, kernelID, displayName,
|
|
notProvidedQ,
|
|
jupyterPath, mathBin,
|
|
fileType,
|
|
processEnvironment,
|
|
baseDir, tempDir,
|
|
wlKernels, (* wlKernelsL(owerCase) *) wlKernelsL,
|
|
commandArgs,
|
|
exitInfo, kernelspecAssoc, kernelspecs,
|
|
conflictMessage, failureMessage
|
|
},
|
|
|
|
kernelScript = FileNameJoin[{projectHome, "WolframLanguageForJupyter", "Resources", "KernelForWolframLanguageForJupyter.wl"}];
|
|
(* just check that the REPL script is there *)
|
|
If[
|
|
!(FileType[kernelScript] === File),
|
|
Print[nopaclet];
|
|
Return[$Failed];
|
|
];
|
|
|
|
jupyterPath = specs["JupyterInstallation"];
|
|
(* if no Jupyter installation path provided, determine it from PATH *)
|
|
If[
|
|
MissingQ[jupyterPath],
|
|
jupyterPath = findJupyterPath[];
|
|
(* if Jupyter not on PATH, message *)
|
|
If[MissingQ[jupyterPath],
|
|
Print[notfound];
|
|
Return[$Failed];
|
|
];
|
|
jupyterPath = FileNameJoin[{jupyterPath, StringJoin["jupyter", fileExt]}];
|
|
];
|
|
|
|
mathBin =
|
|
Lookup[
|
|
specs,
|
|
"WolframEngineBinary",
|
|
(* if no "WolframEngineBinary" provided, use the session Wolfram Kernel location and set notProvidedQ to True *)
|
|
(notProvidedQ = True; mathBinSession)
|
|
];
|
|
|
|
(* check that the Jupyter installation path is a file *)
|
|
If[
|
|
!((fileType = FileType[jupyterPath]) === File),
|
|
Switch[
|
|
fileType,
|
|
Directory,
|
|
Print[isdir];,
|
|
None,
|
|
Print[nobin];
|
|
];
|
|
Return[$Failed];
|
|
];
|
|
|
|
{kernelID, displayName} = {"", ""};
|
|
(* if not clearing, check that the Wolfram Engine installation path is a file, and message appropriately *)
|
|
If[
|
|
!(removeQ && removeAllQ),
|
|
If[
|
|
(fileType = FileType[mathBin]) === File,
|
|
(* get the "Kernel ID" and "Display Name" for the new Jupyter kernel *)
|
|
retrievedNames = getNames[mathBin, TrueQ[notProvidedQ]];
|
|
If[FailureQ[retrievedNames], Print[nolink]; Return[$Failed]];
|
|
{kernelID, displayName} = retrievedNames;,
|
|
Switch[
|
|
fileType,
|
|
Directory,
|
|
Print[isdirMath];,
|
|
None,
|
|
Print[nobinMath];
|
|
];
|
|
Return[$Failed];
|
|
];
|
|
];
|
|
|
|
(* as an association for 11.3 compatibility *)
|
|
processEnvironment = Association[GetEnvironment[]];
|
|
processEnvironment["PATH"] = StringJoin[Environment["PATH"], pathSeperator, DirectoryName[jupyterPath]];
|
|
|
|
(* list of kernels in Jupyter to perform an action on *)
|
|
wlKernels = {kernelID};
|
|
tempDir = "";
|
|
(* if adding, ...*)
|
|
(* otherwise, when removing or clearing, ...*)
|
|
If[
|
|
!removeQ,
|
|
failureMessage = notadded;
|
|
conflictMessage = addconflict;
|
|
|
|
(* create staging directory for files needed to register a kernel with Jupyter *)
|
|
tempDir = CreateDirectory[
|
|
FileNameJoin[{
|
|
projectHome,
|
|
CreateUUID[],
|
|
(* removing this would cause every evalution of addKernelToJupyter adds a new kernel with a different uuid *)
|
|
kernelID
|
|
}], CreateIntermediateDirectories -> True
|
|
];
|
|
|
|
(* export a JSON file to the staging directory that contains all the relevant information on how to run the kernel *)
|
|
Export[
|
|
FileNameJoin[{tempDir, "kernel.json"}],
|
|
Association[
|
|
"argv" -> {mathBin, "-script", kernelScript, "{connection_file}", "ScriptInstall" (* , "-noprompt" *)},
|
|
"display_name" -> displayName,
|
|
"language" -> "Wolfram Language"
|
|
]
|
|
];
|
|
|
|
(* create a list of arguments that directs Jupyter to install from the staging directory *)
|
|
commandArgs = {jupyterPath, "kernelspec", "install", "--user", tempDir};,
|
|
failureMessage = notremoved;
|
|
conflictMessage = removeconflict;
|
|
(* create a list of arguments that directs Jupyter to remove ... *)
|
|
commandArgs = {jupyterPath, "kernelspec", "remove", "-f",
|
|
If[
|
|
!removeAllQ,
|
|
(* just the specified kernel *)
|
|
kernelID,
|
|
(* all Wolfram Language Jupyter kernels *)
|
|
(* select from all kernel IDs in Jupyter those that match the form used by this install *)
|
|
Sequence @@ (wlKernels = Select[getKernels[jupyterPath, processEnvironment], StringMatchQ[#1, (* ("WolframLanguage-" | "wl-") *) "WolframLanguage" ~~ ___, IgnoreCase -> True] &])
|
|
]
|
|
}
|
|
];
|
|
(* if no kernels to act on, quit *)
|
|
If[Length[wlKernels] == 0, Return[];];
|
|
wlKernelsL = ToLowerCase /@ wlKernels;
|
|
|
|
(* for error detection, get a snapshot of kernels before the action is performed *)
|
|
kernelspecs = getKernels[jupyterPath, processEnvironment];
|
|
(* when adding, if there is a kernel with the same id already in Jupyter, it will be replaced; thus, message, but continue *)
|
|
If[Xor[removeQ, SubsetQ[kernelspecs, wlKernelsL]], Print[conflictMessage];];
|
|
|
|
(* perform the action *)
|
|
exitInfo = RunProcess[commandArgs, All, ProcessEnvironment -> processEnvironment];
|
|
(* remove temporary directory if it was created *)
|
|
If[StringLength[tempDir] > 0, DeleteDirectory[DirectoryName[tempDir], DeleteContents -> True]];
|
|
|
|
(* get list of kernels after the action was performed *)
|
|
kernelspecs = getKernels[jupyterPath, processEnvironment];
|
|
(* message about success with respect to the action that was performed *)
|
|
If[
|
|
!Xor[removeQ, SubsetQ[kernelspecs, wlKernelsL]],
|
|
Print[failureMessage];
|
|
Print["configure-jupyter.wls: See below for the message that Jupyter returned when attempting to add the Wolfram Engine."];
|
|
Print[StringTrim[exitInfo["StandardError"], Whitespace]];
|
|
Return[$Failed];
|
|
];
|
|
];
|
|
|
|
(* checking RunProcess ..., and messaging appropriately *)
|
|
If[
|
|
FailureQ[RunProcess[$SystemShell, All, ""]],
|
|
(* maybe remove *)
|
|
If[
|
|
MemberQ[$CommandLine, "-script"],
|
|
Print["configure-jupyter.wls: Please use -file instead of -script in WolframScript."];
|
|
Quit[];
|
|
,
|
|
Print["configure-jupyter.wls: An unknown error has occurred."];
|
|
attemptPathRegeneration[];
|
|
If[FailureQ[RunProcess[$SystemShell, All, ""]], Quit[]];
|
|
];
|
|
];
|
|
|
|
defineGlobalVars[];
|
|
|
|
(* maybe remove *)
|
|
(* checking PATH ..., and messaging appropriately *)
|
|
If[
|
|
Length[splitPath] == 1,
|
|
Print["configure-jupyter.wls: Warning: This script has encountered a very small PATH environment variable."];
|
|
Print["configure-jupyter.wls: Warning: This can occur due to a possible WolframScript bug."];
|
|
attemptPathRegeneration[];
|
|
];
|
|
|
|
|
|
(* START: Building usage message *)
|
|
|
|
templateJupyterPath = StringJoin["\"", FileNameJoin[{"path", "to", "Jupyter-binary"}], "\""];
|
|
templateWLPath = StringJoin["\"", FileNameJoin[{"", "absolute", "path", "to", "Wolfram-Engine-binary--not-wolframscript"}], "\""];
|
|
|
|
(* helpMessage = StringJoin[
|
|
"configure-jupyter.wls add [", templateJupyterPath, "]\n",
|
|
"configure-jupyter.wls adds a Wolfram Engine to a Jupyter binary on PATH, or optional provided Jupyter binary path\n",
|
|
"configure-jupyter.wls add ", templateJupyterPath, " ", templateWLPath, "\n",
|
|
"\tadds the provided absolute Wolfram Engine binary path to the provided Jupyter binary path\n",
|
|
"configure-jupyter.wls remove [", templateJupyterPath ,"]\n",
|
|
"\tremoves any Wolfram Engines found on a Jupyter binary on PATH, or optional provided Jupyter binary path"
|
|
]; *)
|
|
|
|
helpMessage = StringJoin[
|
|
"configure-jupyter.wls add [", templateWLPath, "]\n",
|
|
"\tadds a Wolfram Engine, either attached to the current invocation, or at the provided absolute Wolfram Engine binary path, to a Jupyter binary on PATH\n",
|
|
"configure-jupyter.wls add ", templateWLPath, " ", templateJupyterPath, "\n",
|
|
"\tadds the provided absolute Wolfram Engine binary path to the provided Jupyter binary path\n",
|
|
"configure-jupyter.wls remove [", templateWLPath ,"]\n",
|
|
"\tremoves the Wolfram Engine, either attached to the current invocation, or at the provided absolute Wolfram Engine binary path, from a Jupyter binary on PATH\n",
|
|
"configure-jupyter.wls remove ", templateWLPath, " ", templateJupyterPath, "\n",
|
|
"\tremoves the provided absolute Wolfram Engine binary path from the provided Jupyter binary path\n",
|
|
"configure-jupyter.wls clear [", templateJupyterPath ,"]\n",
|
|
"\tremoves all Wolfram Engines found on a Jupyter binary on PATH, or optional provided Jupyter binary path\n",
|
|
"configure-jupyter.wls build\n",
|
|
"\tbuilds the WolframLanguageForJupyter paclet in the project directory"
|
|
];
|
|
|
|
(* END: Building usage message *)
|
|
|
|
|
|
(* based off of the script invocation, use configureJupyter or PackPaclet; or display help message *)
|
|
If[
|
|
Length[$ScriptCommandLine] < 2 ||
|
|
Length[$ScriptCommandLine] > 4 ||
|
|
$ScriptCommandLine[[2]] === "help",
|
|
Print[helpMessage];
|
|
,
|
|
Switch[
|
|
$ScriptCommandLine[[2]],
|
|
"add" | "Add",
|
|
command = {False, False};,
|
|
"remove" | "Remove",
|
|
command = {True, False};,
|
|
"clear" | "Clear",
|
|
command = {True, True};,
|
|
"build",
|
|
PackPaclet["WolframLanguageForJupyter"];
|
|
Quit[];
|
|
,
|
|
_,
|
|
Print[helpMessage];
|
|
];
|
|
|
|
configureJupyter[
|
|
Switch[
|
|
Length[$ScriptCommandLine],
|
|
4,
|
|
Association[
|
|
"WolframEngineBinary" -> $ScriptCommandLine[[3]],
|
|
"JupyterInstallation" -> $ScriptCommandLine[[4]]
|
|
],
|
|
3,
|
|
If[command === {True, True},
|
|
Association["JupyterInstallation" -> $ScriptCommandLine[[3]]],
|
|
Association["WolframEngineBinary" -> $ScriptCommandLine[[3]]]
|
|
],
|
|
2,
|
|
Association[]
|
|
],
|
|
Sequence @@ command
|
|
];
|
|
];
|
|
|
|
End[];
|