Files
o3de/Code/Tools/ProjectManager/Source/PythonBindings.cpp
T
lumberyard-employee-dm 090aa8f053 Removed ununeeded includes from EBus EBus.h and Policies.h (#4256)
* Removed ununeeded includes from EBus EBus.h and Policies.h

Updated the locations which needed those includes

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Adding missing include for <memory> to AWsClientAuthBus.h

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Remove the while true loop in the EBusQueuePolicy Execute() function

The while true loop in Execute was for allowing additional functions to be queued in the middle of execution of current list of functions.
That functionality was dangerous, because if a queued function added itself during execution unconditionally, then it would result in an infinite loop

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Updated the AssetManager::DispatchEvents function to pump the AssetBus event queue until empty

Queued Events on the AssetBus is able to queue additional events on that Bus during execution of those events.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Changed the AssetManager::DispatchEvents function to only execute the
AssetBus queued events once

Changed the AssetJobsFloodTest.AssetWithNoLoadReference_LoadDependencies_BehaviorObeyed test to dispatch events until the OnAssetContainerReady callback is signaled.

This happens after every asset load to make sure that the
expiring AssetContainer instances are removed from `AssetManager::m_ownedAssetContainer` container before retrying to load the same asset.

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>

* Added a MaxTimeoutSeconds constant for the maximum amount of the time to run a single DispatchEvents loop

Signed-off-by: lumberyard-employee-dm <56135373+lumberyard-employee-dm@users.noreply.github.com>
2021-09-29 18:13:37 -05:00

981 lines
34 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <PythonBindings.h>
#include <ProjectManagerDefs.h>
// Qt defines slots, which interferes with the use here.
#pragma push_macro("slots")
#undef slots
#include <pybind11/functional.h>
#include <pybind11/embed.h>
#include <pybind11/eval.h>
#include <pybind11/stl.h>
#pragma pop_macro("slots")
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <QDir>
namespace Platform
{
bool InsertPythonLibraryPath(
AZStd::unordered_set<AZStd::string>& paths, const char* pythonPackage, const char* engineRoot, const char* subPath)
{
// append lib path to Python paths
AZ::IO::FixedMaxPath libPath = engineRoot;
libPath /= AZ::IO::FixedMaxPathString::format(subPath, pythonPackage);
libPath = libPath.LexicallyNormal();
if (AZ::IO::SystemFile::Exists(libPath.c_str()))
{
paths.insert(libPath.c_str());
return true;
}
AZ_Warning("python", false, "Python library path should exist. path:%s", libPath.c_str());
return false;
}
// Implemented in each different platform's PAL implementation files, as it differs per platform.
AZStd::string GetPythonHomePath(const char* pythonPackage, const char* engineRoot);
} // namespace Platform
#define Py_To_String(obj) pybind11::str(obj).cast<std::string>().c_str()
#define Py_To_String_Optional(dict, key, default_string) dict.contains(key) ? Py_To_String(dict[key]) : default_string
#define QString_To_Py_String(value) pybind11::str(value.toStdString())
#define QString_To_Py_Path(value) m_pathlib.attr("Path")(value.toStdString())
namespace RedirectOutput
{
using RedirectOutputFunc = AZStd::function<void(const char*)>;
struct RedirectOutput
{
PyObject_HEAD RedirectOutputFunc write;
};
PyObject* RedirectWrite(PyObject* self, PyObject* args)
{
std::size_t written(0);
RedirectOutput* selfimpl = reinterpret_cast<RedirectOutput*>(self);
if (selfimpl->write)
{
char* data;
if (!PyArg_ParseTuple(args, "s", &data))
{
return PyLong_FromSize_t(0);
}
selfimpl->write(data);
written = strlen(data);
}
return PyLong_FromSize_t(written);
}
PyObject* RedirectFlush([[maybe_unused]] PyObject* self,[[maybe_unused]] PyObject* args)
{
// no-op
return Py_BuildValue("");
}
PyMethodDef RedirectMethods[] = {
{"write", RedirectWrite, METH_VARARGS, "sys.stdout.write"},
{"flush", RedirectFlush, METH_VARARGS, "sys.stdout.flush"},
{"write", RedirectWrite, METH_VARARGS, "sys.stderr.write"},
{"flush", RedirectFlush, METH_VARARGS, "sys.stderr.flush"},
{0, 0, 0, 0} // sentinel
};
PyTypeObject RedirectOutputType = {
PyVarObject_HEAD_INIT(0, 0) "azlmbr_redirect.RedirectOutputType", // tp_name
sizeof(RedirectOutput), /* tp_basicsize */
0, /* tp_itemsize */
0, /* tp_dealloc */
0, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_reserved */
0, /* tp_repr */
0, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
0, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT, /* tp_flags */
"azlmbr_redirect objects", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
RedirectMethods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
0 /* tp_new */
};
PyModuleDef RedirectOutputModule = {
PyModuleDef_HEAD_INIT, "azlmbr_redirect", 0, -1, 0,
};
// Internal state
PyObject* g_redirect_stdout = nullptr;
PyObject* g_redirect_stdout_saved = nullptr;
PyObject* g_redirect_stderr = nullptr;
PyObject* g_redirect_stderr_saved = nullptr;
PyMODINIT_FUNC PyInit_RedirectOutput(void)
{
g_redirect_stdout = nullptr;
g_redirect_stdout_saved = nullptr;
g_redirect_stderr = nullptr;
g_redirect_stderr_saved = nullptr;
RedirectOutputType.tp_new = PyType_GenericNew;
if (PyType_Ready(&RedirectOutputType) < 0)
{
return 0;
}
PyObject* redirectModule = PyModule_Create(&RedirectOutputModule);
if (redirectModule)
{
Py_INCREF(&RedirectOutputType);
PyModule_AddObject(redirectModule, "Redirect", reinterpret_cast<PyObject*>(&RedirectOutputType));
}
return redirectModule;
}
void SetRedirection(const char* funcname, PyObject*& saved, PyObject*& current, RedirectOutputFunc func)
{
if (PyType_Ready(&RedirectOutputType) < 0)
{
AZ_Warning("python", false, "RedirectOutputType not ready!");
return;
}
if (!current)
{
saved = PySys_GetObject(funcname); // borrowed
current = RedirectOutputType.tp_new(&RedirectOutputType, 0, 0);
}
RedirectOutput* redirectOutput = reinterpret_cast<RedirectOutput*>(current);
redirectOutput->write = func;
PySys_SetObject(funcname, current);
}
void ResetRedirection(const char* funcname, PyObject*& saved, PyObject*& current)
{
if (current)
{
PySys_SetObject(funcname, saved);
}
Py_XDECREF(current);
current = nullptr;
}
PyObject* s_RedirectModule = nullptr;
void Intialize(PyObject* module)
{
s_RedirectModule = module;
SetRedirection("stdout", g_redirect_stdout_saved, g_redirect_stdout, []([[maybe_unused]] const char* msg) {
AZ_TracePrintf("Python", msg);
});
SetRedirection("stderr", g_redirect_stderr_saved, g_redirect_stderr, []([[maybe_unused]] const char* msg) {
AZ_TracePrintf("Python", msg);
});
PySys_WriteStdout("RedirectOutput installed");
}
void Shutdown()
{
ResetRedirection("stdout", g_redirect_stdout_saved, g_redirect_stdout);
ResetRedirection("stderr", g_redirect_stderr_saved, g_redirect_stderr);
Py_XDECREF(s_RedirectModule);
s_RedirectModule = nullptr;
}
} // namespace RedirectOutput
namespace O3DE::ProjectManager
{
PythonBindings::PythonBindings(const AZ::IO::PathView& enginePath)
: m_enginePath(enginePath)
{
m_pythonStarted = StartPython();
}
PythonBindings::~PythonBindings()
{
StopPython();
}
bool PythonBindings::PythonStarted()
{
return m_pythonStarted && Py_IsInitialized();
}
bool PythonBindings::StartPython()
{
if (Py_IsInitialized())
{
AZ_Warning("python", false, "Python is already active");
return false;
}
// set PYTHON_HOME
AZStd::string pyBasePath = Platform::GetPythonHomePath(PY_PACKAGE, m_enginePath.c_str());
if (!AZ::IO::SystemFile::Exists(pyBasePath.c_str()))
{
AZ_Error("python", false, "Python home path does not exist: %s", pyBasePath.c_str());
return false;
}
AZStd::wstring pyHomePath;
AZStd::to_wstring(pyHomePath, pyBasePath);
Py_SetPythonHome(pyHomePath.c_str());
// display basic Python information
AZ_TracePrintf("python", "Py_GetVersion=%s \n", Py_GetVersion());
AZ_TracePrintf("python", "Py_GetPath=%ls \n", Py_GetPath());
AZ_TracePrintf("python", "Py_GetExecPrefix=%ls \n", Py_GetExecPrefix());
AZ_TracePrintf("python", "Py_GetProgramFullPath=%ls \n", Py_GetProgramFullPath());
PyImport_AppendInittab("azlmbr_redirect", RedirectOutput::PyInit_RedirectOutput);
try
{
// ignore system location for sites site-packages
Py_IsolatedFlag = 1; // -I - Also sets Py_NoUserSiteDirectory. If removed PyNoUserSiteDirectory should be set.
Py_IgnoreEnvironmentFlag = 1; // -E
const bool initializeSignalHandlers = true;
pybind11::initialize_interpreter(initializeSignalHandlers);
RedirectOutput::Intialize(PyImport_ImportModule("azlmbr_redirect"));
// Acquire GIL before calling Python code
AZStd::lock_guard<decltype(m_lock)> lock(m_lock);
pybind11::gil_scoped_acquire acquire;
// sanity import check
if (PyRun_SimpleString("import sys") != 0)
{
AZ_Assert(false, "Import sys failed");
return false;
}
// import required modules
m_cmake = pybind11::module::import("o3de.cmake");
m_register = pybind11::module::import("o3de.register");
m_manifest = pybind11::module::import("o3de.manifest");
m_engineTemplate = pybind11::module::import("o3de.engine_template");
m_enableGemProject = pybind11::module::import("o3de.enable_gem");
m_disableGemProject = pybind11::module::import("o3de.disable_gem");
m_editProjectProperties = pybind11::module::import("o3de.project_properties");
m_pathlib = pybind11::module::import("pathlib");
// make sure the engine is registered
RegisterThisEngine();
return !PyErr_Occurred();
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Assert(false, "Py_Initialize() failed with %s", e.what());
return false;
}
}
bool PythonBindings::StopPython()
{
if (Py_IsInitialized())
{
RedirectOutput::Shutdown();
pybind11::finalize_interpreter();
}
else
{
AZ_Warning("ProjectManagerWindow", false, "Did not finalize since Py_IsInitialized() was false");
}
return !PyErr_Occurred();
}
bool PythonBindings::RegisterThisEngine()
{
bool registrationResult = true; // already registered is considered successful
bool pythonResult = ExecuteWithLock(
[&]
{
// check current engine path against all other registered engines
// to see if we are already registered
auto allEngines = m_manifest.attr("get_engines")();
if (pybind11::isinstance<pybind11::list>(allEngines))
{
for (auto engine : allEngines)
{
AZ::IO::FixedMaxPath enginePath(Py_To_String(engine));
if (enginePath.Compare(m_enginePath) == 0)
{
return;
}
}
}
auto result = m_register.attr("register")(QString_To_Py_Path(QString(m_enginePath.c_str())));
registrationResult = (result.cast<int>() == 0);
});
bool finalResult = (registrationResult && pythonResult);
AZ_Assert(finalResult, "Registration of this engine failed!");
return finalResult;
}
AZ::Outcome<void, AZStd::string> PythonBindings::ExecuteWithLockErrorHandling(AZStd::function<void()> executionCallback)
{
if (!Py_IsInitialized())
{
return AZ::Failure<AZStd::string>("Python is not initialized");
}
AZStd::lock_guard<decltype(m_lock)> lock(m_lock);
pybind11::gil_scoped_release release;
pybind11::gil_scoped_acquire acquire;
try
{
executionCallback();
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("PythonBindings", false, "Python exception %s", e.what());
return AZ::Failure<AZStd::string>(e.what());
}
return AZ::Success();
}
bool PythonBindings::ExecuteWithLock(AZStd::function<void()> executionCallback)
{
return ExecuteWithLockErrorHandling(executionCallback).IsSuccess();
}
AZ::Outcome<EngineInfo> PythonBindings::GetEngineInfo()
{
EngineInfo engineInfo;
bool result = ExecuteWithLock([&] {
auto enginePath = m_manifest.attr("get_this_engine_path")();
auto o3deData = m_manifest.attr("load_o3de_manifest")();
if (pybind11::isinstance<pybind11::dict>(o3deData))
{
engineInfo.m_path = Py_To_String(enginePath);
auto defaultGemsFolder = m_manifest.attr("get_o3de_gems_folder")();
engineInfo.m_defaultGemsFolder = Py_To_String_Optional(o3deData, "default_gems_folder", Py_To_String(defaultGemsFolder));
auto defaultProjectsFolder = m_manifest.attr("get_o3de_projects_folder")();
engineInfo.m_defaultProjectsFolder = Py_To_String_Optional(o3deData, "default_projects_folder", Py_To_String(defaultProjectsFolder));
auto defaultRestrictedFolder = m_manifest.attr("get_o3de_restricted_folder")();
engineInfo.m_defaultRestrictedFolder = Py_To_String_Optional(o3deData, "default_restricted_folder", Py_To_String(defaultRestrictedFolder));
auto defaultTemplatesFolder = m_manifest.attr("get_o3de_templates_folder")();
engineInfo.m_defaultTemplatesFolder = Py_To_String_Optional(o3deData, "default_templates_folder", Py_To_String(defaultTemplatesFolder));
auto defaultThirdPartyFolder = m_manifest.attr("get_o3de_third_party_folder")();
engineInfo.m_thirdPartyPath = Py_To_String_Optional(o3deData, "default_third_party_folder", Py_To_String(defaultThirdPartyFolder));
}
auto engineData = m_manifest.attr("get_engine_json_data")(pybind11::none(), enginePath);
if (pybind11::isinstance<pybind11::dict>(engineData))
{
try
{
engineInfo.m_version = Py_To_String_Optional(engineData, "O3DEVersion", "0.0.0.0");
engineInfo.m_name = Py_To_String_Optional(engineData, "engine_name", "O3DE");
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("PythonBindings", false, "Failed to get EngineInfo from %s", Py_To_String(enginePath));
}
}
});
if (!result || !engineInfo.IsValid())
{
return AZ::Failure();
}
else
{
return AZ::Success(AZStd::move(engineInfo));
}
}
bool PythonBindings::SetEngineInfo(const EngineInfo& engineInfo)
{
bool result = ExecuteWithLock([&] {
auto registrationResult = m_register.attr("register")(
QString_To_Py_Path(engineInfo.m_path),
pybind11::none(), // project_path
pybind11::none(), // gem_path
pybind11::none(), // external_subdir_path
pybind11::none(), // template_path
pybind11::none(), // restricted_path
pybind11::none(), // repo_uri
pybind11::none(), // default_engines_folder
QString_To_Py_Path(engineInfo.m_defaultProjectsFolder),
QString_To_Py_Path(engineInfo.m_defaultGemsFolder),
QString_To_Py_Path(engineInfo.m_defaultTemplatesFolder),
pybind11::none(), // default_restricted_folder
QString_To_Py_Path(engineInfo.m_thirdPartyPath)
);
if (registrationResult.cast<int>() != 0)
{
result = false;
}
});
return result;
}
AZ::Outcome<GemInfo> PythonBindings::GetGemInfo(const QString& path, const QString& projectPath)
{
GemInfo gemInfo = GemInfoFromPath(QString_To_Py_String(path), QString_To_Py_Path(projectPath));
if (gemInfo.IsValid())
{
return AZ::Success(AZStd::move(gemInfo));
}
else
{
return AZ::Failure();
}
}
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetEngineGemInfos()
{
QVector<GemInfo> gems;
auto result = ExecuteWithLockErrorHandling([&]
{
for (auto path : m_manifest.attr("get_engine_gems")())
{
gems.push_back(GemInfoFromPath(path, pybind11::none()));
}
});
if (!result.IsSuccess())
{
return AZ::Failure<AZStd::string>(result.GetError().c_str());
}
std::sort(gems.begin(), gems.end());
return AZ::Success(AZStd::move(gems));
}
AZ::Outcome<QVector<GemInfo>, AZStd::string> PythonBindings::GetAllGemInfos(const QString& projectPath)
{
QVector<GemInfo> gems;
auto result = ExecuteWithLockErrorHandling([&]
{
auto pyProjectPath = QString_To_Py_Path(projectPath);
for (auto path : m_manifest.attr("get_all_gems")(pyProjectPath))
{
gems.push_back(GemInfoFromPath(path, pyProjectPath));
}
});
if (!result.IsSuccess())
{
return AZ::Failure<AZStd::string>(result.GetError().c_str());
}
std::sort(gems.begin(), gems.end());
return AZ::Success(AZStd::move(gems));
}
AZ::Outcome<QVector<AZStd::string>, AZStd::string> PythonBindings::GetEnabledGemNames(const QString& projectPath)
{
// Retrieve the path to the cmake file that lists the enabled gems.
pybind11::str enabledGemsFilename;
auto result = ExecuteWithLockErrorHandling([&]
{
enabledGemsFilename = m_cmake.attr("get_enabled_gem_cmake_file")(
pybind11::none(), // project_name
QString_To_Py_Path(projectPath)); // project_path
});
if (!result.IsSuccess())
{
return AZ::Failure<AZStd::string>(result.GetError().c_str());
}
// Retrieve the actual list of names from the cmake file.
QVector<AZStd::string> gemNames;
result = ExecuteWithLockErrorHandling([&]
{
const auto pyGemNames = m_cmake.attr("get_enabled_gems")(enabledGemsFilename);
for (auto gemName : pyGemNames)
{
gemNames.push_back(Py_To_String(gemName));
}
});
if (!result.IsSuccess())
{
return AZ::Failure<AZStd::string>(result.GetError().c_str());
}
return AZ::Success(AZStd::move(gemNames));
}
bool PythonBindings::AddProject(const QString& path)
{
bool registrationResult = false;
bool result = ExecuteWithLock(
[&]
{
auto projectPath = QString_To_Py_Path(path);
auto pythonRegistrationResult = m_register.attr("register")(pybind11::none(), projectPath);
// Returns an exit code so boolify it then invert result
registrationResult = !pythonRegistrationResult.cast<bool>();
});
return result && registrationResult;
}
bool PythonBindings::RemoveProject(const QString& path)
{
bool registrationResult = false;
bool result = ExecuteWithLock(
[&]
{
auto pythonRegistrationResult = m_register.attr("register")(
pybind11::none(), // engine_path
QString_To_Py_Path(path), // project_path
pybind11::none(), // gem_path
pybind11::none(), // external_subdir_path
pybind11::none(), // template_path
pybind11::none(), // restricted_path
pybind11::none(), // repo_uri
pybind11::none(), // default_engines_folder
pybind11::none(), // default_projects_folder
pybind11::none(), // default_gems_folder
pybind11::none(), // default_templates_folder
pybind11::none(), // default_restricted_folder
pybind11::none(), // default_third_party_folder
pybind11::none(), // external_subdir_engine_path
pybind11::none(), // external_subdir_project_path
true, // remove
false // force
);
// Returns an exit code so boolify it then invert result
registrationResult = !pythonRegistrationResult.cast<bool>();
});
return result && registrationResult;
}
AZ::Outcome<ProjectInfo> PythonBindings::CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo)
{
ProjectInfo createdProjectInfo;
bool result = ExecuteWithLock([&] {
auto projectPath = QString_To_Py_Path(projectInfo.m_path);
auto createProjectResult = m_engineTemplate.attr("create_project")(
projectPath,
QString_To_Py_String(projectInfo.m_projectName),
QString_To_Py_Path(projectTemplatePath)
);
if (createProjectResult.cast<int>() == 0)
{
createdProjectInfo = ProjectInfoFromPath(projectPath);
}
});
if (!result || !createdProjectInfo.IsValid())
{
return AZ::Failure();
}
else
{
return AZ::Success(AZStd::move(createdProjectInfo));
}
}
AZ::Outcome<ProjectInfo> PythonBindings::GetProject(const QString& path)
{
ProjectInfo projectInfo = ProjectInfoFromPath(QString_To_Py_Path(path));
if (projectInfo.IsValid())
{
return AZ::Success(AZStd::move(projectInfo));
}
else
{
return AZ::Failure();
}
}
GemInfo PythonBindings::GemInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath)
{
GemInfo gemInfo;
gemInfo.m_path = Py_To_String(path);
gemInfo.m_directoryLink = gemInfo.m_path;
auto data = m_manifest.attr("get_gem_json_data")(pybind11::none(), path, pyProjectPath);
if (pybind11::isinstance<pybind11::dict>(data))
{
try
{
// required
gemInfo.m_name = Py_To_String(data["gem_name"]);
// optional
gemInfo.m_displayName = Py_To_String_Optional(data, "display_name", gemInfo.m_name);
gemInfo.m_summary = Py_To_String_Optional(data, "summary", "");
gemInfo.m_version = "";
gemInfo.m_requirement = Py_To_String_Optional(data, "requirements", "");
gemInfo.m_creator = Py_To_String_Optional(data, "origin", "");
gemInfo.m_documentationLink = Py_To_String_Optional(data, "documentation_url", "");
if (gemInfo.m_creator.contains("Open 3D Engine"))
{
gemInfo.m_gemOrigin = GemInfo::GemOrigin::Open3DEEngine;
}
if (data.contains("user_tags"))
{
for (auto tag : data["user_tags"])
{
gemInfo.m_features.push_back(Py_To_String(tag));
}
}
if (data.contains("dependencies"))
{
for (auto dependency : data["dependencies"])
{
gemInfo.m_dependencies.push_back(Py_To_String(dependency));
}
}
QString gemType = Py_To_String_Optional(data, "type", "");
if (gemType == "Asset")
{
gemInfo.m_types |= GemInfo::Type::Asset;
}
if (gemType == "Code")
{
gemInfo.m_types |= GemInfo::Type::Code;
}
if (gemType == "Tool")
{
gemInfo.m_types |= GemInfo::Type::Tool;
}
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("PythonBindings", false, "Failed to get GemInfo for gem %s", Py_To_String(path));
}
}
return gemInfo;
}
ProjectInfo PythonBindings::ProjectInfoFromPath(pybind11::handle path)
{
ProjectInfo projectInfo;
projectInfo.m_path = Py_To_String(path);
projectInfo.m_needsBuild = false;
auto projectData = m_manifest.attr("get_project_json_data")(pybind11::none(), path);
if (pybind11::isinstance<pybind11::dict>(projectData))
{
try
{
projectInfo.m_projectName = Py_To_String(projectData["project_name"]);
projectInfo.m_displayName = Py_To_String_Optional(projectData, "display_name", projectInfo.m_projectName);
projectInfo.m_origin = Py_To_String_Optional(projectData, "origin", projectInfo.m_origin);
projectInfo.m_summary = Py_To_String_Optional(projectData, "summary", projectInfo.m_summary);
projectInfo.m_iconPath = Py_To_String_Optional(projectData, "icon", ProjectPreviewImagePath);
if (projectData.contains("user_tags"))
{
for (auto tag : projectData["user_tags"])
{
projectInfo.m_userTags.append(Py_To_String(tag));
}
}
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("PythonBindings", false, "Failed to get ProjectInfo for project %s", Py_To_String(path));
}
}
return projectInfo;
}
AZ::Outcome<QVector<ProjectInfo>> PythonBindings::GetProjects()
{
QVector<ProjectInfo> projects;
bool result = ExecuteWithLock([&] {
// external projects
for (auto path : m_manifest.attr("get_projects")())
{
projects.push_back(ProjectInfoFromPath(path));
}
// projects from the engine
for (auto path : m_manifest.attr("get_engine_projects")())
{
projects.push_back(ProjectInfoFromPath(path));
}
});
if (!result)
{
return AZ::Failure();
}
else
{
return AZ::Success(AZStd::move(projects));
}
}
AZ::Outcome<void, AZStd::string> PythonBindings::AddGemToProject(const QString& gemPath, const QString& projectPath)
{
return ExecuteWithLockErrorHandling([&]
{
m_enableGemProject.attr("enable_gem_in_project")(
pybind11::none(), // gem name not needed as path is provided
QString_To_Py_Path(gemPath),
pybind11::none(), // project name not needed as path is provided
QString_To_Py_Path(projectPath)
);
});
}
AZ::Outcome<void, AZStd::string> PythonBindings::RemoveGemFromProject(const QString& gemPath, const QString& projectPath)
{
return ExecuteWithLockErrorHandling([&]
{
m_disableGemProject.attr("disable_gem_in_project")(
pybind11::none(), // gem name not needed as path is provided
QString_To_Py_Path(gemPath),
pybind11::none(), // project name not needed as path is provided
QString_To_Py_Path(projectPath)
);
});
}
bool PythonBindings::RemoveInvalidProjects()
{
bool removalResult = false;
bool result = ExecuteWithLock(
[&]
{
auto pythonRemovalResult = m_register.attr("remove_invalid_o3de_projects")();
// Returns an exit code so boolify it then invert result
removalResult = !pythonRemovalResult.cast<bool>();
});
return result && removalResult;
}
AZ::Outcome<void, AZStd::string> PythonBindings::UpdateProject(const ProjectInfo& projectInfo)
{
bool updateProjectSucceeded = false;
auto result = ExecuteWithLockErrorHandling([&]
{
std::list<std::string> newTags;
for (const auto& i : projectInfo.m_userTags)
{
newTags.push_back(i.toStdString());
}
auto editResult = m_editProjectProperties.attr("edit_project_props")(
QString_To_Py_Path(projectInfo.m_path),
pybind11::none(), // proj_name not used
QString_To_Py_String(projectInfo.m_projectName),
QString_To_Py_String(projectInfo.m_origin),
QString_To_Py_String(projectInfo.m_displayName),
QString_To_Py_String(projectInfo.m_summary),
QString_To_Py_String(projectInfo.m_iconPath), // new_icon
pybind11::none(), // add_tags not used
pybind11::none(), // remove_tags not used
pybind11::list(pybind11::cast(newTags)));
updateProjectSucceeded = (editResult.cast<int>() == 0);
});
if (!result.IsSuccess())
{
return result;
}
else if (!updateProjectSucceeded)
{
return AZ::Failure<AZStd::string>("Failed to update project.");
}
return AZ::Success();
}
ProjectTemplateInfo PythonBindings::ProjectTemplateInfoFromPath(pybind11::handle path, pybind11::handle pyProjectPath)
{
ProjectTemplateInfo templateInfo;
templateInfo.m_path = Py_To_String(path);
auto data = m_manifest.attr("get_template_json_data")(pybind11::none(), path, pyProjectPath);
if (pybind11::isinstance<pybind11::dict>(data))
{
try
{
// required
templateInfo.m_displayName = Py_To_String(data["display_name"]);
templateInfo.m_name = Py_To_String(data["template_name"]);
templateInfo.m_summary = Py_To_String(data["summary"]);
// optional
if (data.contains("canonical_tags"))
{
for (auto tag : data["canonical_tags"])
{
templateInfo.m_canonicalTags.push_back(Py_To_String(tag));
}
}
if (data.contains("user_tags"))
{
for (auto tag : data["user_tags"])
{
templateInfo.m_canonicalTags.push_back(Py_To_String(tag));
}
}
QString templateProjectPath = QDir(templateInfo.m_path).filePath("Template");
auto enabledGemNames = GetEnabledGemNames(templateProjectPath);
if (enabledGemNames)
{
for (auto gem : enabledGemNames.GetValue())
{
// Exclude the template ${Name} placeholder for the list of included gems
// That Gem gets created with the project
if (!gem.contains("${Name}"))
{
templateInfo.m_includedGems.push_back(Py_To_String(gem.c_str()));
}
}
}
}
catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("PythonBindings", false, "Failed to get ProjectTemplateInfo for %s", Py_To_String(path));
}
}
return templateInfo;
}
AZ::Outcome<QVector<ProjectTemplateInfo>> PythonBindings::GetProjectTemplates(const QString& projectPath)
{
QVector<ProjectTemplateInfo> templates;
bool result = ExecuteWithLock([&] {
for (auto path : m_manifest.attr("get_templates_for_project_creation")())
{
templates.push_back(ProjectTemplateInfoFromPath(path, QString_To_Py_Path(projectPath)));
}
});
if (!result)
{
return AZ::Failure();
}
else
{
return AZ::Success(AZStd::move(templates));
}
}
AZ::Outcome<void, AZStd::string> PythonBindings::AddGemRepo(const QString& repoUri)
{
// o3de scripts need method added
(void)repoUri;
return AZ::Failure<AZStd::string>("Adding Gem Repo not implemented yet in o3de scripts.");
}
GemRepoInfo PythonBindings::GemRepoInfoFromPath(pybind11::handle path, pybind11::handle pyEnginePath)
{
/* Placeholder Logic */
(void)path;
(void)pyEnginePath;
return GemRepoInfo();
}
//#define MOCK_GEM_REPO_INFO true
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> PythonBindings::GetAllGemRepoInfos()
{
QVector<GemRepoInfo> gemRepos;
#ifndef MOCK_GEM_REPO_INFO
auto result = ExecuteWithLockErrorHandling(
[&]
{
/* Placeholder Logic, o3de scripts need method added
*
for (auto path : m_manifest.attr("get_gem_repos")())
{
gemRepos.push_back(GemRepoInfoFromPath(path, pybind11::none()));
}
*
*/
});
if (!result.IsSuccess())
{
return AZ::Failure<AZStd::string>(result.GetError().c_str());
}
#else
GemRepoInfo mockJohnRepo("JohnCreates", "John Smith", QDateTime(QDate(2021, 8, 31), QTime(11, 57)), true);
mockJohnRepo.m_summary = "John's Summary. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sollicitudin dapibus urna";
mockJohnRepo.m_repoLink = "https://github.com/o3de/o3de";
mockJohnRepo.m_additionalInfo = "John's additional info. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sollicitu.";
gemRepos.push_back(mockJohnRepo);
GemRepoInfo mockJaneRepo("JanesGems", "Jane Doe", QDateTime(QDate(2021, 9, 10), QTime(18, 23)), false);
mockJaneRepo.m_summary = "Jane's Summary.";
mockJaneRepo.m_repoLink = "https://github.com/o3de/o3de.org";
gemRepos.push_back(mockJaneRepo);
#endif // MOCK_GEM_REPO_INFO
std::sort(gemRepos.begin(), gemRepos.end());
return AZ::Success(AZStd::move(gemRepos));
}
}