-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathPythonSetup.cs
More file actions
262 lines (219 loc) · 9.52 KB
/
Copy pathPythonSetup.cs
File metadata and controls
262 lines (219 loc) · 9.52 KB
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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
// Copyright Gradientspace Corp. All Rights Reserved.
using System.Diagnostics;
using System.Runtime.InteropServices;
using Python.Runtime;
namespace GSPython
{
public static class PythonSetup
{
// useful documentation: https://github.com/pythonnet/pythonnet/wiki
internal static bool bIsPythonInitialized = false;
private static IntPtr BeginAllThreadsHandle = IntPtr.Zero;
public struct PythonInstallation
{
public Version PythonVersion = new Version(); // this is dumb, only will work if versions are all #.#.#
public string Path = "";
public string PythonDLLPath = "";
public PythonInstallation() { }
}
public static bool IsPythonAvailable { get { return bIsPythonInitialized; } }
public static bool InitializePython(List<string>? OutputMessages = null)
{
if (bIsPythonInitialized)
return true;
// all the installed python versions we found
List<PythonInstallation> PythonVersions = new List<PythonInstallation>();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
FindPython_Win(PythonVersions);
if (PythonVersions.Count == 0) {
OutputMessages?.Add("[GSPython] Could not find a suitable Python installation/DLL! Python will not be available.");
return false;
}
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
FindPython_OSX(PythonVersions);
if (PythonVersions.Count == 0) {
OutputMessages?.Add("[GSPython] Could not find a suitable Python Framework installation! Must install full OSX Framework from python.org/downloads.");
return false;
}
}
if (PythonVersions.Count == 0) {
OutputMessages?.Add("[GSPython] Current platform does not support Python");
return false;
}
// largest version number first
PythonVersions.Sort((PythonInstallation a, PythonInstallation b) => { return a.PythonVersion.CompareTo(b.PythonVersion); });
PythonVersions.Reverse();
foreach (var version in PythonVersions)
OutputMessages?.Add($"[GSPython] Found Python {version.PythonVersion} installation at {version.Path}");
Python.Runtime.Runtime.PythonDLL = PythonVersions[0].PythonDLLPath;
string initMessage = $"[GSPython] Trying to Initialize PythonEngine with DLL {PythonVersions[0].PythonDLLPath}...";
try {
PythonEngine.Initialize();
// ??? does BeginAllowThreads() block or not-block the GIL thing?
// see https://github.com/pythonnet/pythonnet/wiki/Threading
BeginAllThreadsHandle = PythonEngine.BeginAllowThreads();
OutputMessages?.Add(initMessage + "Ok!");
} catch (Exception ex) {
OutputMessages?.Add(initMessage);
OutputMessages?.Add($"[GSPython] PythonEngine Initialization Failed : {ex.Message}");
return false;
}
bIsPythonInitialized = true;
return true;
}
internal static void FindPython_Win(List<PythonInstallation> PythonVersions)
{
// try to detect a python installation in a folder and find the pythonXYZ.dll file
var try_add_python_version = (string rootpath) =>
{
try
{
string dirname = Path.GetFileName(rootpath);
if (dirname.StartsWith("Python", StringComparison.InvariantCultureIgnoreCase))
{
// does this always work? assuming if we are in folder PythonXYZ then dll will be pythonXYZ.dll
int VersionFromFolder = int.Parse(dirname.Substring(6));
string dllpath = Path.Combine(rootpath, "python" + VersionFromFolder.ToString() + ".dll");
if (File.Exists(dllpath) == false)
return;
// check if we already found this exact dll, ie were already called with this rootpath
// via some other means (possible if we are iterating through different possible install locations)
int ExistingIndex = PythonVersions.FindIndex((PythonInstallation p) =>
{
return Path.GetFullPath(p.PythonDLLPath) == Path.GetFullPath(dllpath);
});
if (ExistingIndex != -1)
return;
// need python.exe to get version number
string exePath = Path.Combine(rootpath, "python.exe");
if (File.Exists(exePath) == false)
return;
// run python.exe --version to get version number
Process process = new Process();
process.StartInfo.FileName = exePath;
process.StartInfo.Arguments = "--version";
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
StreamReader reader = process.StandardOutput;
string output = reader.ReadToEnd().Trim();
process.WaitForExit();
// assume printed output is in form "Python 13.3.1" etc
output = output.Replace("Python ", "");
System.Version.TryParse(output, out Version? FoundVersion);
if (FoundVersion == null)
return;
PythonVersions.Add(new PythonInstallation() { PythonVersion = FoundVersion, Path = rootpath, PythonDLLPath = dllpath });
}
}
catch (Exception) { }
};
// search in Users\<username>\AppData\Local\Programs\Python\Python###
// this is the default installation folder for windows python installer if not installed for all users...
string AppDataLocal =
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "..", "Local");
string StandardPythonInstallDir = Path.Combine(AppDataLocal, "Programs\\Python");
if (Directory.Exists(StandardPythonInstallDir))
{
string[] subdirs = Directory.GetDirectories(StandardPythonInstallDir);
foreach (string subdir in subdirs)
try_add_python_version(subdir);
}
// look in Program Files/Python###, this is default windows path if installed for all users
string ProgramFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
if (Directory.Exists(ProgramFiles))
{
string[] ProgramFilesPythonSubdirs = Directory.GetDirectories(ProgramFiles, "Python*");
foreach (string subdir in ProgramFilesPythonSubdirs)
try_add_python_version(subdir);
}
// todo look in C:\Python## ? does python still install there sometimes?
// todo python may be in the system path...
//string PathVariable = Environment.GetEnvironmentVariable("Path") ?? "";
//string[] subpaths = PathVariable.Split(';');
}
internal static void FindPython_OSX(List<PythonInstallation> PythonVersions)
{
// try to detect a python installation in a folder and find the pythonXYZ.dll file
var try_add_python_version_osx = (string rootpath) =>
{
try
{
// TODO: assuming root python folder is just the version name
string fullversion = Path.GetFileName(rootpath);
//if (dirname.StartsWith("Python", StringComparison.InvariantCultureIgnoreCase))
if (true)
{
string dylibpath = Path.Combine(rootpath, "lib", "libpython" + fullversion + ".dylib");
if (File.Exists(dylibpath) == false)
return;
// check if we already found this exact dll, ie were already called with this rootpath
// via some other means (possible if we are iterating through different possible install locations)
int ExistingIndex = PythonVersions.FindIndex((PythonInstallation p) =>
{
return Path.GetFullPath(p.PythonDLLPath) == Path.GetFullPath(dylibpath);
});
if (ExistingIndex != -1)
return;
// need python.exe to get version number
string exePath = Path.Combine(rootpath, "bin", "python3");
if (File.Exists(exePath) == false)
return;
// run python.exe --version to get version number
Process process = new Process();
process.StartInfo.FileName = exePath;
process.StartInfo.Arguments = "--version";
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
StreamReader reader = process.StandardOutput;
string output = reader.ReadToEnd().Trim();
process.WaitForExit();
// assume printed output is in form "Python 13.3.1" etc
output = output.Replace("Python ", "");
System.Version.TryParse(output, out Version? FoundVersion);
if (FoundVersion == null)
return;
PythonVersions.Add(new PythonInstallation() { PythonVersion = FoundVersion, Path = rootpath, PythonDLLPath = dylibpath });
}
}
catch (Exception) { }
};
// yikes this is a horrible hack but I'm not sure how else to do it...
for (int k = 50; k >= 1; --k)
{
string py_framework_path = $"/Library/Frameworks/Python.framework/Versions/3.{k}";
if (Directory.Exists(py_framework_path))
try_add_python_version_osx(py_framework_path);
}
}
public static void PythonShutdown()
{
if (bIsPythonInitialized)
{
PythonEngine.EndAllowThreads(BeginAllThreadsHandle);
// TODO
// PythonEngine uses BinaryFormatter internally, which was deprecated in dotnet 8 and
// removed in dotnet 9. Seems to only be used on Shutdown()?
// Waiting for library update to see if this gets resolved...
// See issue here: https://github.com/pythonnet/pythonnet/issues/2282
try
{
AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", true); // doesn't work on dotnet 9
PythonEngine.Shutdown();
AppContext.SetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", false);
}
catch (Exception ex)
{
Debug.WriteLine("Exception thrown by PythonEngine.Shutdown(): " + ex.Message);
}
bIsPythonInitialized = false;
}
}
}
}