Skip to content

Commit b8114db

Browse files
authored
Updates to fix crash in O3DE caused by the python relocation (o3de#17623)
1. Relocation of Python Packages and venv to ~/.o3de/Python (Independent of the 3rdParty folder) 2. Symlinking python3.10.so instead of doing a copy 3. Extra PhysX 4/5 Cleanup Signed-off-by: Steve Pham <[email protected]>
1 parent bcfca1b commit b8114db

36 files changed

+212
-242
lines changed

Code/Framework/AzCore/AzCore/Utils/Utils.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,14 @@ namespace AZ::Utils
428428
return AZ::Success(thirdPartyFolder);
429429
}
430430

431+
AZ::IO::FixedMaxPathString GetO3dePythonVenvRoot(AZ::SettingsRegistryInterface* settingsRegistry /*= nullptr*/)
432+
{
433+
// Locate the manifest directory
434+
auto manifestDirectory = AZ::IO::FixedMaxPath(GetO3deManifestDirectory(settingsRegistry)) / "Python" / "venv";
435+
return manifestDirectory.Native();
436+
}
437+
438+
431439
template AZ::Outcome<AZStd::string, AZStd::string> ReadFile(AZStd::string_view filePath, size_t maxFileSize);
432440
template AZ::Outcome<AZStd::vector<int8_t>, AZStd::string> ReadFile(AZStd::string_view filePath, size_t maxFileSize);
433441
template AZ::Outcome<AZStd::vector<uint8_t>, AZStd::string> ReadFile(AZStd::string_view filePath, size_t maxFileSize);

Code/Framework/AzCore/AzCore/Utils/Utils.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,11 @@ namespace AZ
176176
//! Returns the outcome of the request, the configured 3rd Party path if successful, the error message if not.
177177
AZ::Outcome<AZStd::string, AZStd::string> Get3rdPartyDirectory(AZ::SettingsRegistryInterface* settingsRegistry = nullptr);
178178

179+
//! Retrieves the full directory to the O3DE Python virtual environment (venv) folder.
180+
//! @param settingsRegistry pointer to the SettingsRegistry to use for lookup
181+
//! If nullptr, the AZ::Interface instance of the SettingsRegistry is used
182+
AZ::IO::FixedMaxPathString GetO3dePythonVenvRoot(AZ::SettingsRegistryInterface* settingsRegistry = nullptr);
183+
179184

180185
//! error code value returned when GetEnv fails
181186
enum class GetEnvErrorCode

Code/Framework/AzCore/Platform/Common/UnixLike/AzCore/Utils/Utils_UnixLike.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,16 @@ namespace AZ::Utils
3939
}
4040
}
4141

42-
if (const char* homePath = std::getenv("HOME"); homePath != nullptr)
42+
struct passwd* pass = getpwuid(getuid());
43+
if (pass)
4344
{
44-
AZ::IO::FixedMaxPath path{homePath};
45+
AZ::IO::FixedMaxPath path{pass->pw_dir};
4546
return path.Native();
4647
}
4748

48-
struct passwd* pass = getpwuid(getuid());
49-
if (pass)
49+
if (const char* homePath = std::getenv("HOME"); homePath != nullptr)
5050
{
51-
AZ::IO::FixedMaxPath path{pass->pw_dir};
51+
AZ::IO::FixedMaxPath path{homePath};
5252
return path.Native();
5353
}
5454

Code/Framework/AzCore/Platform/Windows/AzCore/Utils/Utils_Windows.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
*/
88

99
#include <AzCore/Utils/Utils.h>
10+
#include <AzCore/IO/Path/Path.h>
1011
#include <AzCore/PlatformIncl.h>
1112
#include <AzCore/std/string/conversions.h>
1213

1314
#include <stdlib.h>
15+
#include <shlobj_core.h>
1416

1517
namespace AZ::Utils
1618
{
@@ -41,6 +43,14 @@ namespace AZ::Utils
4143
}
4244
}
4345

46+
wchar_t sysUserProfilePathW[MAX_PATH];
47+
if (SUCCEEDED(SHGetFolderPath(0, CSIDL_PROFILE, 0, SHGFP_TYPE_DEFAULT, sysUserProfilePathW)))
48+
{
49+
AZ::IO::FixedMaxPathString sysUserProfilePathStr;
50+
AZStd::to_string(sysUserProfilePathStr, sysUserProfilePathW);
51+
return sysUserProfilePathStr;
52+
}
53+
4454
char userProfileBuffer[AZ::IO::MaxPathLength]{};
4555
size_t variableSize = 0;
4656
auto err = getenv_s(&variableSize, userProfileBuffer, AZ::IO::MaxPathLength, "USERPROFILE");

Code/Framework/AzToolsFramework/AzToolsFramework/API/PythonLoader.cpp

Lines changed: 41 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,18 @@
77
*/
88

99
#include <AzToolsFramework/API/PythonLoader.h>
10-
#include <AzToolsFramework_Traits_Platform.h>
1110
#include <AzCore/Component/ComponentApplicationBus.h>
1211
#include <AzCore/IO/FileIO.h>
1312
#include <AzCore/IO/GenericStreams.h>
1413
#include <AzCore/IO/Path/Path.h>
14+
#include <AzCore/IO/SystemFile.h>
1515
#include <AzCore/Math/Sha1.h>
1616
#include <AzCore/std/containers/vector.h>
1717
#include <AzCore/std/string/string.h>
1818
#include <AzCore/std/string/string_view.h>
1919
#include <AzCore/std/string/conversions.h>
2020
#include <AzCore/std/string/tokenize.h>
21+
#include <AzCore/Serialization/Json/JsonUtils.h>
2122
#include <AzCore/Settings/ConfigParser.h>
2223
#include <AzCore/Utils/Utils.h>
2324
#include <AzFramework/IO/LocalFileIO.h>
@@ -26,82 +27,56 @@ namespace AzToolsFramework::EmbeddedPython
2627
{
2728
PythonLoader::PythonLoader()
2829
{
29-
#if AZ_TRAIT_PYTHON_LOADER_ENABLE_EXPLICIT_LOADING
30-
// PYTHON_SHARED_LIBRARY_PATH must be defined in the build scripts and referencing the path to the python shared library
31-
#if !defined(PYTHON_SHARED_LIBRARY_PATH)
32-
#error "PYTHON_SHARED_LIBRARY_PATH is not defined"
33-
#endif
34-
35-
// Construct the path to the shared python library within the venv folder
36-
AZ::IO::FixedMaxPath engineRoot = AZ::IO::FixedMaxPath(AZ::Utils::GetEnginePath());
37-
AZ::IO::FixedMaxPath thirdPartyRoot = PythonLoader::GetDefault3rdPartyPath(false);
38-
AZ::IO::FixedMaxPath pythonVenvPath = PythonLoader::GetPythonVenvPath(thirdPartyRoot, engineRoot);
39-
40-
AZ::IO::PathView libPythonName = AZ::IO::PathView(PYTHON_SHARED_LIBRARY_PATH).Filename();
41-
AZ::IO::FixedMaxPath pythonVenvLibPath = pythonVenvPath / "lib" / libPythonName;
42-
43-
m_embeddedLibPythonModuleHandle = AZ::DynamicModuleHandle::Create(pythonVenvLibPath.StringAsPosix().c_str(), false);
44-
bool loadResult = m_embeddedLibPythonModuleHandle->Load(false, true);
45-
AZ_Error("PythonLoader", loadResult, "Failed to load %s.\n", libPythonName.StringAsPosix().c_str());
46-
#endif // AZ_TRAIT_PYTHON_LOADER_ENABLE_EXPLICIT_LOADING
47-
}
48-
49-
PythonLoader::~PythonLoader()
50-
{
51-
#if AZ_TRAIT_PYTHON_LOADER_ENABLE_EXPLICIT_LOADING
52-
AZ_Assert(m_embeddedLibPythonModuleHandle, "DynamicModuleHandle for python was not created");
53-
m_embeddedLibPythonModuleHandle->Unload();
54-
#endif // AZ_TRAIT_PYTHON_LOADER_ENABLE_EXPLICIT_LOADING
55-
}
30+
#if defined(IMPLICIT_LOAD_PYTHON_SHARED_LIBRARY)
5631

57-
AZ::IO::FixedMaxPath PythonLoader::GetDefault3rdPartyPath(bool createOnDemand)
58-
{
59-
AZ::IO::FixedMaxPath thirdPartyEnvPathPath;
60-
61-
// The highest priority for the 3rd party path is the environment variable 'LY_3RDPARTY_PATH'
62-
static constexpr const char* env3rdPartyKey = "LY_3RDPARTY_PATH";
63-
char env3rdPartyPath[AZ::IO::MaxPathLength] = { '\0' };
64-
auto envOutcome = AZ::Utils::GetEnv(AZStd::span(env3rdPartyPath), env3rdPartyKey);
65-
if (envOutcome && (strlen(env3rdPartyPath) > 0))
66-
{
67-
// If so, then use the path that is set as the third party path
68-
thirdPartyEnvPathPath = AZ::IO::FixedMaxPath(env3rdPartyPath).LexicallyNormal();
69-
}
70-
// The next priority is to read the 3rd party directory from the manifest file
71-
else if (auto manifest3rdPartyResult = AZ::Utils::Get3rdPartyDirectory(); manifest3rdPartyResult.IsSuccess())
32+
// Determine if this is an sdk-engine build. For SDK engines, we want to prevent implicit python module loading.
33+
[[maybe_unused]] bool isSdkEngine{ false };
34+
auto engineSettingsPath = AZ::IO::FixedMaxPath{ AZ::Utils::GetEnginePath() } / "engine.json";
35+
if (AZ::IO::SystemFile::Exists(engineSettingsPath.c_str()))
7236
{
73-
thirdPartyEnvPathPath = manifest3rdPartyResult.GetValue();
37+
auto loadOutcome = AZ::JsonSerializationUtils::ReadJsonFile(engineSettingsPath.c_str());
38+
if (loadOutcome.IsSuccess())
39+
{
40+
auto& doc = loadOutcome.GetValue();
41+
rapidjson::Value::MemberIterator sdkEngineFieldIter = doc.FindMember("sdk_engine");
42+
if (sdkEngineFieldIter != doc.MemberEnd())
43+
{
44+
isSdkEngine = sdkEngineFieldIter->value.GetBool();
45+
}
46+
}
7447
}
75-
// Fallback to the default 3rd Party path based on the location of the manifest folder
76-
else
48+
if (!isSdkEngine)
7749
{
78-
auto manifestPath = AZ::Utils::GetO3deManifestDirectory();
79-
thirdPartyEnvPathPath = AZ::IO::FixedMaxPath(manifestPath) / "3rdParty";
50+
m_embeddedLibPythonModuleHandle = AZ::DynamicModuleHandle::Create(IMPLICIT_LOAD_PYTHON_SHARED_LIBRARY, false);
51+
bool loadResult = m_embeddedLibPythonModuleHandle->Load(false, true);
52+
AZ_Error("PythonLoader", loadResult, "Failed to load " IMPLICIT_LOAD_PYTHON_SHARED_LIBRARY "\n");
8053
}
54+
#endif // IMPLICIT_LOAD_PYTHON_SHARED_LIBRARY
55+
}
8156

82-
if ((!AZ::IO::SystemFile::IsDirectory(thirdPartyEnvPathPath.c_str())) && createOnDemand)
57+
PythonLoader::~PythonLoader()
58+
{
59+
#if defined(IMPLICIT_LOAD_PYTHON_SHARED_LIBRARY)
60+
if (m_embeddedLibPythonModuleHandle)
8361
{
84-
auto createPathResult = AZ::IO::SystemFile::CreateDir(thirdPartyEnvPathPath.c_str());
85-
AZ_Assert(createPathResult, "Unable to create missing 3rd Party Folder '%s'", thirdPartyEnvPathPath.c_str())
62+
m_embeddedLibPythonModuleHandle->Unload();
8663
}
87-
return thirdPartyEnvPathPath;
64+
#endif // IMPLICIT_LOAD_PYTHON_SHARED_LIBRARY
8865
}
8966

90-
AZ::IO::FixedMaxPath PythonLoader::GetPythonHomePath(AZ::IO::PathView engineRoot)
67+
AZ::IO::FixedMaxPath PythonLoader::GetPythonHomePath(AZ::IO::PathView engineRoot, const char* overridePythonBaseVenvPath /*= nullptr*/)
9168
{
92-
AZ::IO::FixedMaxPath thirdPartyFolder = GetDefault3rdPartyPath(true);
93-
9469
// The python HOME path relative to the executable depends on the host platform the package is created for
9570
#if AZ_TRAIT_PYTHON_LOADER_PYTHON_HOME_BIN_SUBPATH
96-
AZ::IO::FixedMaxPath pythonHomePath = PythonLoader::GetPythonExecutablePath(thirdPartyFolder, engineRoot).ParentPath();
71+
AZ::IO::FixedMaxPath pythonHomePath = PythonLoader::GetPythonExecutablePath(engineRoot, overridePythonBaseVenvPath).ParentPath();
9772
#else
98-
AZ::IO::FixedMaxPath pythonHomePath = PythonLoader::GetPythonExecutablePath(thirdPartyFolder, engineRoot);
73+
AZ::IO::FixedMaxPath pythonHomePath = PythonLoader::GetPythonExecutablePath(engineRoot, overridePythonBaseVenvPath);
9974
#endif // AZ_TRAIT_PYTHON_LOADER_PYTHON_HOME_BIN_SUBPATH
10075

10176
return pythonHomePath;
10277
}
10378

104-
AZ::IO::FixedMaxPath PythonLoader::GetPythonVenvPath(AZ::IO::PathView thirdPartyRoot, AZ::IO::PathView engineRoot)
79+
AZ::IO::FixedMaxPath PythonLoader::GetPythonVenvPath(AZ::IO::PathView engineRoot, const char* overridePythonBaseVenvPath /*= nullptr*/)
10580
{
10681
// Perform the same hash calculation as cmake/CalculateEnginePathId.cmake
10782
/////
@@ -118,16 +93,18 @@ namespace AzToolsFramework::EmbeddedPython
11893
hasher.GetDigest(digest);
11994

12095
// Construct the path to where the python venv based on the engine path should be located
121-
AZ::IO::FixedMaxPath libPath = thirdPartyRoot;
96+
AZ::IO::FixedMaxPath libPath = (overridePythonBaseVenvPath != nullptr) ? AZ::IO::FixedMaxPath(overridePythonBaseVenvPath) :
97+
AZ::Utils::GetO3dePythonVenvRoot();
98+
12299
// The ID is based on the first 32 bits of the digest, and formatted to at least 8-character wide hexadecimal representation
123-
libPath /= AZ::IO::FixedMaxPathString::format("venv/%08x", digest[0]);
100+
libPath /= AZ::IO::FixedMaxPathString::format("%08x", digest[0]);
124101
libPath = libPath.LexicallyNormal();
125102
return libPath;
126103
}
127104

128-
AZ::IO::FixedMaxPath PythonLoader::GetPythonExecutablePath(AZ::IO::PathView thirdPartyRoot, AZ::IO::PathView engineRoot)
105+
AZ::IO::FixedMaxPath PythonLoader::GetPythonExecutablePath(AZ::IO::PathView engineRoot, const char* overridePythonBaseVenvPath /*= nullptr*/)
129106
{
130-
AZ::IO::FixedMaxPath pythonVenvConfig = PythonLoader::GetPythonVenvPath(thirdPartyRoot, engineRoot) / "pyvenv.cfg";
107+
AZ::IO::FixedMaxPath pythonVenvConfig = PythonLoader::GetPythonVenvPath(engineRoot, overridePythonBaseVenvPath) / "pyvenv.cfg";
131108
AZ::IO::SystemFileStream systemFileStream;
132109
if (!systemFileStream.Open(pythonVenvConfig.c_str(), AZ::IO::OpenMode::ModeRead))
133110
{
@@ -151,11 +128,11 @@ namespace AzToolsFramework::EmbeddedPython
151128
return AZ::IO::FixedMaxPath(pythonHome);
152129
}
153130

154-
void PythonLoader::ReadPythonEggLinkPaths(AZ::IO::PathView thirdPartyRoot, AZ::IO::PathView engineRoot, EggLinkPathVisitor resultPathCallback)
131+
void PythonLoader::ReadPythonEggLinkPaths(AZ::IO::PathView engineRoot, EggLinkPathVisitor resultPathCallback, const char* overridePythonBaseVenvPath /*= nullptr*/)
155132
{
156133
// Get the python venv path
157134
AZ::IO::FixedMaxPath pythonVenvSitePackages =
158-
AZ::IO::FixedMaxPath(PythonLoader::GetPythonVenvPath(thirdPartyRoot, engineRoot)) / O3DE_PYTHON_SITE_PACKAGE_SUBPATH;
135+
AZ::IO::FixedMaxPath(PythonLoader::GetPythonVenvPath(engineRoot, overridePythonBaseVenvPath)) / O3DE_PYTHON_SITE_PACKAGE_SUBPATH;
159136

160137
// Always add the site-packages folder from the virtual environment into the path list
161138
resultPathCallback(pythonVenvSitePackages.LexicallyNormal());

Code/Framework/AzToolsFramework/AzToolsFramework/API/PythonLoader.h

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,35 +26,32 @@ namespace AzToolsFramework::EmbeddedPython
2626

2727
//! Calculate the python home (PYTHONHOME) based on the engine root
2828
//! @param engineRoot The path to the engine root to locate the python home
29+
//! @param overridePythonBaseVenvPath If set, the path override the base python venv folder
2930
//! @return The path of the python home path
30-
static AZ::IO::FixedMaxPath GetPythonHomePath(AZ::IO::PathView engineRoot);
31+
static AZ::IO::FixedMaxPath GetPythonHomePath(AZ::IO::PathView engineRoot, const char* overridePythonBaseVenvPath = nullptr);
3132

3233
//! Collect the paths from all the egg-link files found in the python home
3334
//! paths used by the engine
34-
//! @param thirdPartyRoot The root location of the O3DE 3rdParty folder
3535
//! @param engineRoot The path to the engine root to locate the python home
3636
//! @param eggLinkPathVisitor The callback visitor function to receive the egg-link paths that are discovered
37+
//! @param overridePythonBaseVenvPath If set, the path override the base python venv folder
3738
using EggLinkPathVisitor = AZStd::function<void(AZ::IO::PathView)>;
38-
static void ReadPythonEggLinkPaths(AZ::IO::PathView thirdPartyRoot, AZ::IO::PathView engineRoot, EggLinkPathVisitor eggLinkPathVisitor);
39-
40-
//! Get the default 3rd Party folder path.
41-
//! @return The path of the 3rd Party root path
42-
static AZ::IO::FixedMaxPath GetDefault3rdPartyPath(bool createOnDemand);
39+
static void ReadPythonEggLinkPaths(AZ::IO::PathView engineRoot, EggLinkPathVisitor eggLinkPathVisitor, const char* overridePythonBaseVenvPath = nullptr);
4340

4441
//! Calculate the path to the engine's python virtual environment used for
4542
//! python home (PYTHONHOME) based on the engine root
46-
//! @param thirdPartyRoot The root location of the O3DE 3rdParty folder
4743
//! @param engineRoot The path to the engine root to locate the python venv path
44+
//! @param overridePythonBaseVenvPath If set, the path override the base python venv folder
4845
//! @return The path of the python venv path
49-
static AZ::IO::FixedMaxPath GetPythonVenvPath(AZ::IO::PathView thirdPartyRoot, AZ::IO::PathView engineRoot);
46+
static AZ::IO::FixedMaxPath GetPythonVenvPath(AZ::IO::PathView engineRoot, const char* overridePythonBaseVenvPath = nullptr);
5047

5148
//! Calculate the path to the where the python executable resides in. Note that this
5249
//! is not always the same path as the python home path
53-
//! @param thirdPartyRoot The root location of the O3DE 3rdParty folder
5450
//! @param engineRoot The path to the engine root to
51+
//! @param overridePythonBaseVenvPath If set, the path override the base python venv folder
5552
//! locate the python executable path
5653
//! @return The path of the python venv path
57-
static AZ::IO::FixedMaxPath GetPythonExecutablePath(AZ::IO::PathView thirdPartyRoot, AZ::IO::PathView engineRoot);
54+
static AZ::IO::FixedMaxPath GetPythonExecutablePath(AZ::IO::PathView engineRoot, const char* overridePythonBaseVenvPath = nullptr);
5855

5956
private:
6057
AZStd::unique_ptr<AZ::DynamicModuleHandle> m_embeddedLibPythonModuleHandle;

Code/Framework/AzToolsFramework/Platform/Linux/AzToolsFramework_Traits_Linux.h

Lines changed: 0 additions & 11 deletions
This file was deleted.

Code/Framework/AzToolsFramework/Platform/Linux/AzToolsFramework_Traits_Platform.h

Lines changed: 0 additions & 10 deletions
This file was deleted.

Code/Framework/AzToolsFramework/Platform/Linux/platform_linux.cmake

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#
77
#
88

9-
get_target_property(libraries 3rdParty::Python INTERFACE_LINK_LIBRARIES)
10-
11-
set(LY_COMPILE_DEFINITIONS PRIVATE PYTHON_SHARED_LIBRARY_PATH="${libraries}")
9+
set(LY_COMPILE_DEFINITIONS
10+
PRIVATE
11+
IMPLICIT_LOAD_PYTHON_SHARED_LIBRARY="${LY_PYTHON_SHARED_LIB}"
12+
AZ_TRAIT_PYTHON_LOADER_PYTHON_HOME_BIN_SUBPATH
13+
)

Code/Framework/AzToolsFramework/Platform/Linux/platform_linux_files.cmake

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,5 @@
77
#
88

99
set(FILES
10-
AzToolsFramework_Traits_Linux.h
11-
AzToolsFramework_Traits_Platform.h
1210
AzToolsFramework/API/EditorAssetSystemAPI_Linux.cpp
1311
)

0 commit comments

Comments
 (0)