Skip to content

Commit c1392fb

Browse files
author
ikrima
committed
Ryan changes to make Python plugin independent of system installation
1 parent 817995d commit c1392fb

2 files changed

Lines changed: 90 additions & 14 deletions

File tree

‎Source/UnrealEnginePython/Private/UnrealEnginePython.cpp‎

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -147,26 +147,31 @@ FAutoConsoleCommand ExecPythonScriptCommand(
147147
void FUnrealEnginePythonModule::StartupModule()
148148
{
149149
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
150-
FString IniValue;
151-
if (GConfig->GetString(UTF8_TO_TCHAR("Python"), UTF8_TO_TCHAR("Home"), IniValue, GEngineIni)) {
150+
FString PythonHome;
151+
if (GConfig->GetString(UTF8_TO_TCHAR("Python"), UTF8_TO_TCHAR("Home"), PythonHome, GEngineIni)) {
152152
#if PY_MAJOR_VERSION >= 3
153-
wchar_t *home = (wchar_t *)*IniValue;
153+
wchar_t *home = (wchar_t *)*PythonHome;
154154
#else
155-
char *home = TCHAR_TO_UTF8(*IniValue);
155+
char *home = TCHAR_TO_UTF8(*PythonHome);
156156
#endif
157+
FPlatformMisc::SetEnvironmentVar(TEXT("PYTHONHOME"), *PythonHome);
157158
Py_SetPythonHome(home);
158159
}
159160

160-
if (GConfig->GetString(UTF8_TO_TCHAR("Python"), UTF8_TO_TCHAR("RelativeHome"), IniValue, GEngineIni)) {
161-
IniValue = FPaths::Combine(*FPaths::GameContentDir(), *IniValue);
161+
if (GConfig->GetString(UTF8_TO_TCHAR("Python"), UTF8_TO_TCHAR("RelativeHome"), PythonHome, GEngineIni)) {
162+
PythonHome = FPaths::Combine(*FPaths::GameContentDir(), *PythonHome);
163+
FPaths::NormalizeFilename(PythonHome);
164+
PythonHome = FPaths::ConvertRelativePathToFull(PythonHome);
162165
#if PY_MAJOR_VERSION >= 3
163-
wchar_t *home = (wchar_t *)*IniValue;
166+
wchar_t *home = (wchar_t *)*PythonHome;
164167
#else
165-
char *home = TCHAR_TO_UTF8(*IniValue);
168+
char *home = TCHAR_TO_UTF8(*PythonHome);
166169
#endif
170+
167171
Py_SetPythonHome(home);
168172
}
169173

174+
FString IniValue;
170175
if (GConfig->GetString(UTF8_TO_TCHAR("Python"), UTF8_TO_TCHAR("ProgramName"), IniValue, GEngineIni)) {
171176
#if PY_MAJOR_VERSION >= 3
172177
wchar_t *program_name = (wchar_t *)*IniValue;
@@ -178,6 +183,8 @@ void FUnrealEnginePythonModule::StartupModule()
178183

179184
if (GConfig->GetString(UTF8_TO_TCHAR("Python"), UTF8_TO_TCHAR("RelativeProgramName"), IniValue, GEngineIni)) {
180185
IniValue = FPaths::Combine(*FPaths::GameContentDir(), *IniValue);
186+
FPaths::NormalizeFilename(IniValue);
187+
IniValue = FPaths::ConvertRelativePathToFull(IniValue);
181188
#if PY_MAJOR_VERSION >= 3
182189
wchar_t *program_name = (wchar_t *)*IniValue;
183190
#else
@@ -218,6 +225,45 @@ void FUnrealEnginePythonModule::StartupModule()
218225
ZipPath = FPaths::Combine(*FPaths::GameContentDir(), UTF8_TO_TCHAR("ue_python.zip"));
219226
}
220227

228+
// To ensure there are no path conflicts, if we have a valid python home at this point,
229+
// we override the current environment entirely with the environment we want to use,
230+
// removing any paths to other python environments we aren't using.
231+
if (PythonHome.Len() > 0)
232+
{
233+
FPlatformMisc::SetEnvironmentVar(TEXT("PYTHONHOME"), *PythonHome);
234+
235+
const int32 MaxPathVarLen = 32768;
236+
FString OrigPathVar = FString::ChrN(MaxPathVarLen, TEXT('\0'));
237+
FPlatformMisc::GetEnvironmentVariable(TEXT("PATH"), OrigPathVar.GetCharArray().GetData(), MaxPathVarLen);
238+
239+
// Get the current path and remove elements with python in them, we don't want any conflicts
240+
const TCHAR* PathDelimiter = FPlatformMisc::GetPathVarDelimiter();
241+
TArray<FString> PathVars;
242+
OrigPathVar.ParseIntoArray(PathVars, PathDelimiter, true);
243+
for (int32 PathRemoveIndex = PathVars.Num() - 1; PathRemoveIndex >= 0; --PathRemoveIndex)
244+
{
245+
if (PathVars[PathRemoveIndex].Contains(TEXT("python"), ESearchCase::IgnoreCase))
246+
{
247+
UE_LOG(LogPython, Verbose, TEXT("Removing other python Path: '%s'"), *PathVars[PathRemoveIndex]);
248+
PathVars.RemoveAt(PathRemoveIndex);
249+
}
250+
}
251+
252+
// Setup our own paths for PYTHONPATH
253+
TArray<FString> OurPythonPaths = {
254+
PythonHome,
255+
FPaths::Combine(PythonHome, TEXT("Lib")),
256+
FPaths::Combine(PythonHome, TEXT("Lib/site-packages")),
257+
};
258+
FString PythonPathVars = FString::Join(OurPythonPaths, PathDelimiter);
259+
FPlatformMisc::SetEnvironmentVar(TEXT("PYTHONPATH"), *PythonPathVars);
260+
261+
// Also add our paths to PATH, just so any searching will find our local python
262+
PathVars.Append(OurPythonPaths);
263+
FString ModifiedPath = FString::Join(PathVars, PathDelimiter);
264+
FPlatformMisc::SetEnvironmentVar(TEXT("PATH"), *ModifiedPath);
265+
}
266+
221267
Py_Initialize();
222268

223269
PyEval_InitThreads();

‎Source/UnrealEnginePython/UnrealEnginePython.Build.cs‎

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class UnrealEnginePython : ModuleRules
1919

2020
private string[] windowsKnownPaths =
2121
{
22-
"C:/knl/python3",
22+
"../../../../../ThirdParty/Python3",
2323
"C:/Program Files/Python36",
2424
"C:/Program Files/Python35",
2525
"C:/Python27",
@@ -171,6 +171,19 @@ public UnrealEnginePython(TargetInfo Target)
171171
string libPath = GetWindowsPythonLibFile(pythonHome);
172172
PublicLibraryPaths.Add(Path.GetDirectoryName(libPath));
173173
PublicAdditionalLibraries.Add(libPath);
174+
175+
// copy the dlls into the plugins dlls folder, so they don't have to be on the path
176+
string dllsDir = Path.Combine(ModuleDirectory, "../../Binaries", Target.Platform == UnrealTargetPlatform.Win32 ? "Win32" : "Win64");
177+
try
178+
{
179+
File.Copy(Path.Combine(pythonHome, "python3.dll"), Path.Combine(dllsDir, "python3.dll"), true);
180+
File.Copy(Path.Combine(pythonHome, "python36.dll"), Path.Combine(dllsDir, "python36.dll"), true);
181+
}
182+
catch(System.IO.IOException) { }
183+
catch(System.UnauthorizedAccessException)
184+
{
185+
System.Console.WriteLine("WARNING: Unable to copy python dlls, they are probably in use...");
186+
}
174187
}
175188
else if (Target.Platform == UnrealTargetPlatform.Mac)
176189
{
@@ -217,6 +230,15 @@ public UnrealEnginePython(TargetInfo Target)
217230

218231
}
219232

233+
private bool IsPathRelative(string Path)
234+
{
235+
bool IsRooted = Path.StartsWith("\\", System.StringComparison.Ordinal) || // Root of the current directory on Windows. Also covers "\\" for UNC or "network" paths.
236+
Path.StartsWith("/", System.StringComparison.Ordinal) || // Root of the current directory on Windows, root on UNIX-likes.
237+
// Also covers "\\", considering normalization replaces "\\" with "//".
238+
(Path.Length >= 2 && char.IsLetter(Path[0]) && Path[1] == ':'); // Starts with "<DriveLetter>:"
239+
return !IsRooted;
240+
}
241+
220242
private string DiscoverPythonPath(string[] knownPaths)
221243
{
222244
// insert the PYTHONHOME content as the first known path
@@ -227,16 +249,23 @@ private string DiscoverPythonPath(string[] knownPaths)
227249

228250
foreach (string path in paths)
229251
{
230-
string headerFile = Path.Combine(path, "include", "Python.h");
252+
string actualPath = path;
253+
254+
if (IsPathRelative(actualPath))
255+
{
256+
actualPath = Path.GetFullPath(Path.Combine(ModuleDirectory, actualPath));
257+
}
258+
259+
string headerFile = Path.Combine(actualPath, "include", "Python.h");
231260
if (File.Exists(headerFile))
232261
{
233-
return path;
262+
return actualPath;
234263
}
235264
// this is mainly useful for OSX
236-
headerFile = Path.Combine(path, "Headers", "Python.h");
265+
headerFile = Path.Combine(actualPath, "Headers", "Python.h");
237266
if (File.Exists(headerFile))
238267
{
239-
return path;
268+
return actualPath;
240269
}
241270
}
242271
return "";
@@ -327,7 +356,8 @@ private string GetWindowsPythonLibFile(string basePath)
327356
}
328357
if (!found)
329358
{
330-
System.Console.WriteLine("[WARNING] Your Python installation is not in the system PATH environment variable, very probably the plugin will fail to load");
359+
System.Console.WriteLine("[WARNING] Your Python installation is not in the system PATH environment variable.");
360+
System.Console.WriteLine("[WARNING] Ensure your python paths are set in GlobalConfig (DefaultEngine.ini) so the path can be corrected at runtime.");
331361
}
332362
// first try with python3
333363
for (int i = 9; i >= 0; i--)

0 commit comments

Comments
 (0)