Initial Python bindings pass for ProjectManager

Adds dependency on pybind and interface to call o3de.py functions from c++
main
Alex Peterson 5 years ago committed by GitHub
parent 2e32b2ee57
commit 26d886792d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -13,6 +13,13 @@ if(NOT PAL_TRAIT_BUILD_HOST_TOOLS)
return()
endif()
# This will set python_package_name to whatever the package 'Python' is associated with
ly_get_package_association(Python python_package_name)
if (NOT python_package_name)
set(python_package_name "python-no-package-assocation-found")
message(WARNING "Python was not found in the package assocation list. Did someone call ly_associate_package(xxxxxxx Python) ?")
endif()
ly_add_target(
NAME ProjectManager APPLICATION
OUTPUT_NAME o3de
@ -22,7 +29,13 @@ ly_add_target(
AUTORCC
FILES_CMAKE
project_manager_files.cmake
Platform/${PAL_PLATFORM_NAME}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
PLATFORM_INCLUDE_FILES
Platform/${PAL_PLATFORM_NAME}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
Platform/Common/${PAL_TRAIT_COMPILER_ID}/projectmanager_${PAL_TRAIT_COMPILER_ID_LOWERCASE}.cmake
COMPILE_DEFINITIONS
PRIVATE
PY_PACKAGE="${python_package_name}"
INCLUDE_DIRECTORIES
PUBLIC
.
@ -34,6 +47,8 @@ ly_add_target(
3rdParty::Qt::Core
3rdParty::Qt::Concurrent
3rdParty::Qt::Widgets
3rdParty::Python
3rdParty::pybind11
AZ::AzCore
AZ::AzFramework
AZ::AzToolsFramework

@ -0,0 +1,15 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(LY_COMPILE_OPTIONS
PRIVATE
-fexceptions # The macro PYBIND11_EMBEDDED_MODULE uses a try catch block
)

@ -0,0 +1,15 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(LY_COMPILE_OPTIONS
PRIVATE
/EHsc # The macro PYBIND11_EMBEDDED_MODULE uses a try catch block
)

@ -8,3 +8,4 @@
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#

@ -0,0 +1,14 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
Python_linux.cpp
)

@ -0,0 +1,42 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/PlatformDef.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/IO/Path/Path.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace Platform
{
extern bool InsertPythonLibraryPath(AZStd::unordered_set<AZStd::string>& paths, const char* pythonPackage, const char* engineRoot, const char* subPath);
bool InsertPythonBinaryLibraryPaths(AZStd::unordered_set<AZStd::string>& paths, const char* pythonPackage, const char* engineRoot)
{
bool succeeded = true;
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/python/lib");
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/python/lib/python3.7/lib-dynload");
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/python/lib/python3.7");
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/python/lib/python3.7/site-packages");
return succeeded;
}
AZStd::string GetPythonHomePath(const char* pythonPackage, const char* engineRoot)
{
// append lib path to Python paths
AZ::IO::FixedMaxPath libPath = engineRoot;
libPath /= AZ::IO::FixedMaxPathString::format("python/runtime/%s/python", pythonPackage);
libPath = libPath.LexicallyNormal();
return libPath.String();
}
}

@ -0,0 +1,14 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
Python_mac.cpp
)

@ -0,0 +1,43 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/PlatformDef.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/IO/Path/Path.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace Platform
{
extern bool InsertPythonLibraryPath(AZStd::unordered_set<AZStd::string>& paths, const char* pythonPackage, const char* engineRoot, const char* subPath);
bool InsertPythonBinaryLibraryPaths(AZStd::unordered_set<AZStd::string>& paths, const char* pythonPackage, const char* engineRoot)
{
bool succeeded = true;
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/Python.framework/Versions/3.7/lib");
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/Python.framework/Versions/3.7/lib/python3.7/lib-dynload");
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/Python.framework/Versions/3.7/lib/python3.7");
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/Python.framework/Versions/3.7/lib/python3.7/site-packages");
return succeeded;
}
AZStd::string GetPythonHomePath(const char* pythonPackage, const char* engineRoot)
{
// append lib path to Python paths
AZ::IO::FixedMaxPath libPath = engineRoot;
libPath /= AZ::IO::FixedMaxPathString::format("python/runtime/%s/Python.framework/Versions/3.7", pythonPackage);
libPath = libPath.LexicallyNormal();
return libPath.String();
}
}

@ -8,3 +8,8 @@
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(LY_COMPILE_DEFINITIONS
PRIVATE
HAVE_ROUND # defined for Windows since http://p-nand-q.com/python/building-python-33-with-vs2013.html
)

@ -0,0 +1,14 @@
#
# All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
# its licensors.
#
# For complete copyright and license terms please see the LICENSE at the root of this
# distribution (the "License"). All use of this software is governed by the License,
# or, if provided, by the license below or the license accompanying this file. Do not
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#
set(FILES
Python_windows.cpp
)

@ -0,0 +1,43 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <AzCore/PlatformDef.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/IO/Path/Path.h>
#include <AzFramework/StringFunc/StringFunc.h>
namespace Platform
{
extern bool InsertPythonLibraryPath(AZStd::unordered_set<AZStd::string>& paths, const char* pythonPackage, const char* engineRoot, const char* subPath);
bool InsertPythonBinaryLibraryPaths(AZStd::unordered_set<AZStd::string>& paths, const char* pythonPackage, const char* engineRoot)
{
bool succeeded = true;
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/python");
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/python/lib");
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/python/lib/site-packages");
succeeded = succeeded && InsertPythonLibraryPath(paths, pythonPackage, engineRoot, "python/runtime/%s/python/DLLs");
return succeeded;
}
AZStd::string GetPythonHomePath(const char* pythonPackage, const char* engineRoot)
{
// append lib path to Python paths
AZ::IO::FixedMaxPath libPath = engineRoot;
libPath /= AZ::IO::FixedMaxPathString::format("python/runtime/%s/python", pythonPackage);
libPath = libPath.LexicallyNormal();
return libPath.String();
}
}

@ -37,6 +37,7 @@ namespace O3DE::ProjectManager
GemInfo(const QString& name, const QString& creator, const QString& summary, Platforms platforms, bool isAdded);
QString m_path;
QString m_name;
QString m_displayName;
AZ::Uuid m_uuid;

@ -0,0 +1,24 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "ProjectInfo.h"
namespace O3DE::ProjectManager
{
ProjectInfo::ProjectInfo(const QString& path, const QString& projectName, const QString& productName, const AZ::Uuid projectId)
: m_path(path)
, m_projectName(projectName)
, m_productName(productName)
, m_projectId(projectId)
{
}
} // namespace O3DE::ProjectManager

@ -0,0 +1,36 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Math/Uuid.h>
#include <QString>
#endif
namespace O3DE::ProjectManager
{
class ProjectInfo
{
public:
ProjectInfo() = default;
ProjectInfo(const QString& path, const QString& projectName, const QString& productName, const AZ::Uuid projectId);
// from o3de_manifest.json and o3de_projects.json
QString m_path;
// from project.json
QString m_projectName;
QString m_productName;
AZ::Uuid m_projectId;
};
} // namespace O3DE::ProjectManager

@ -28,6 +28,8 @@ namespace O3DE::ProjectManager
{
m_ui->setupUi(this);
m_pythonBindings = AZStd::make_unique<PythonBindings>(engineRootPath);
ConnectSlotsAndSignals();
QDir rootDir = QString::fromUtf8(engineRootPath.Native().data(), aznumeric_cast<int>(engineRootPath.Native().size()));
@ -44,6 +46,7 @@ namespace O3DE::ProjectManager
ProjectManagerWindow::~ProjectManagerWindow()
{
m_pythonBindings.reset();
}
void ProjectManagerWindow::BuildScreens()

@ -17,7 +17,7 @@
#include <QMainWindow>
#include <QStackedWidget>
#include <AzCore/IO/Path/Path_fwd.h>
#include <PythonBindings.h>
#endif
namespace Ui
@ -52,6 +52,7 @@ namespace O3DE::ProjectManager
private:
QScopedPointer<Ui::ProjectManagerWindowClass> m_ui;
AZStd::unique_ptr<PythonBindings> m_pythonBindings;
};
} // namespace O3DE::ProjectManager

@ -14,6 +14,8 @@
#include <Source/ui_ProjectsHome.h>
#include <PythonBindingsInterface.h>
namespace O3DE::ProjectManager
{
ProjectsHome::ProjectsHome(ProjectManagerWindow* window)
@ -23,6 +25,9 @@ namespace O3DE::ProjectManager
m_ui->setupUi(this);
ConnectSlotsAndSignals();
// example of how to get the current project name
ProjectInfo currentProject = PythonBindingsInterface::Get()->GetCurrentProject();
}
ProjectsHome::~ProjectsHome()

@ -0,0 +1,155 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <PythonBindings.h>
// Qt defines slots, which interferes with the use here.
#pragma push_macro("slots")
#undef slots
#include <Python.h>
#include <pybind11/functional.h>
#include <pybind11/pybind11.h>
#include <pybind11/embed.h>
#include <pybind11/eval.h>
#pragma pop_macro("slots")
#include <AzCore/IO/FileIO.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/std/string/conversions.h>
#include <AzCore/StringFunc/StringFunc.h>
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 implentation files, as it differs per platform.
AZStd::string GetPythonHomePath(const char* pythonPackage, const char* engineRoot);
} // namespace Platform
namespace O3DE::ProjectManager
{
PythonBindings::PythonBindings(const AZ::IO::PathView& enginePath)
: m_enginePath(enginePath)
{
StartPython();
}
PythonBindings::~PythonBindings()
{
StopPython();
}
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_Warning("python", false, "Python home path must exist. path:%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());
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);
// Acquire GIL before calling Python code
AZStd::lock_guard<decltype(m_lock)> lock(m_lock);
pybind11::gil_scoped_acquire acquire;
// Setup sys.path
int result = PyRun_SimpleString("import sys");
AZ_Warning("ProjectManagerWindow", result != -1, "Import sys failed");
result = PyRun_SimpleString(AZStd::string::format("sys.path.append('%s')", m_enginePath.c_str()).c_str());
AZ_Warning("ProjectManagerWindow", result != -1, "Append to sys path failed");
return result == 0 && !PyErr_Occurred();
} catch ([[maybe_unused]] const std::exception& e)
{
AZ_Warning("python", false, "Py_Initialize() failed with %s", e.what());
return false;
}
}
bool PythonBindings::StopPython()
{
if (Py_IsInitialized())
{
pybind11::finalize_interpreter();
}
else
{
AZ_Warning("python", false, "Did not finalize since Py_IsInitialized() was false");
}
return !PyErr_Occurred();
}
void PythonBindings::ExecuteWithLock(AZStd::function<void()> executionCallback)
{
AZStd::lock_guard<decltype(m_lock)> lock(m_lock);
pybind11::gil_scoped_release release;
pybind11::gil_scoped_acquire acquire;
executionCallback();
}
ProjectInfo PythonBindings::GetCurrentProject()
{
ProjectInfo project;
ExecuteWithLock([&] {
auto currentProjectTool = pybind11::module::import("cmake.Tools.current_project");
auto getCurrentProject = currentProjectTool.attr("get_current_project");
auto currentProject = getCurrentProject(m_enginePath.c_str());
project.m_path = currentProject.cast<std::string>().c_str();
});
return project;
}
}

@ -0,0 +1,41 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <PythonBindingsInterface.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/std/parallel/semaphore.h>
namespace O3DE::ProjectManager
{
class PythonBindings
: public PythonBindingsInterface::Registrar
{
public:
PythonBindings() = default;
PythonBindings(const AZ::IO::PathView& enginePath);
~PythonBindings() override;
// PythonBindings overrides
ProjectInfo GetCurrentProject() override;
private:
AZ_DISABLE_COPY_MOVE(PythonBindings);
void ExecuteWithLock(AZStd::function<void()> executionCallback);
bool StartPython();
bool StopPython();
AZ::IO::FixedMaxPath m_enginePath;
AZStd::recursive_mutex m_lock;
};
}

@ -0,0 +1,39 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/containers/vector.h>
#include <GemCatalog/GemInfo.h>
#include <ProjectInfo.h>
namespace O3DE::ProjectManager
{
//! Interface used to interact with the o3de cli python functions
class IPythonBindings
{
public:
AZ_RTTI(O3DE::ProjectManager::IPythonBindings, "{C2B72CA4-56A9-4601-A584-3B40E83AA17C}");
AZ_DISABLE_COPY_MOVE(IPythonBindings);
IPythonBindings() = default;
virtual ~IPythonBindings() = default;
//! Get the current project
virtual ProjectInfo GetCurrentProject() = 0;
};
using PythonBindingsInterface = AZ::Interface<IPythonBindings>;
} // namespace O3DE::ProjectManager

@ -35,6 +35,10 @@ int main(int argc, char* argv[])
QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough);
AzQtComponents::Utilities::HandleDpiAwareness(AzQtComponents::Utilities::SystemDpiAware);
AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
int runSuccess = 0;
{
QApplication app(argc, argv);
// Need to use settings registry to get EngineRootFolder
@ -51,5 +55,9 @@ int main(int argc, char* argv[])
O3DE::ProjectManager::ProjectManagerWindow window(nullptr, engineRootPath);
window.show();
return app.exec();
runSuccess = app.exec();
}
AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
return runSuccess;
}

@ -19,9 +19,14 @@ set(FILES
Source/FirstTimeUse.h
Source/FirstTimeUse.cpp
Source/FirstTimeUse.ui
Source/ProjectInfo.h
Source/ProjectInfo.cpp
Source/ProjectManagerWindow.h
Source/ProjectManagerWindow.cpp
Source/ProjectManagerWindow.ui
Source/PythonBindings.h
Source/PythonBindings.cpp
Source/PythonBindingsInterface.h
Source/NewProjectSettings.h
Source/NewProjectSettings.cpp
Source/NewProjectSettings.ui

Loading…
Cancel
Save