o3de / sig-core

5 stars 6 forks source link

Proposed RFC Feature : Python Relocation and Virtual Environments #60

Closed spham-amzn closed 6 months ago

spham-amzn commented 9 months ago

Summary:

The download and installation of Python should be moved out of the engine and into a package outside of the engine root, and subsequent uses of Python should be done from a virtual environments instead of directly in the downloaded package.

What is the relevance of this feature?

Moving Python out of the engine path and into the LY_3RDPARTY_PATH with the other 3rd Party packages will provide the following benefits:

Python virtual environments provides a mechanism to isolate different packages and libraries for a specific application from other applications. Since virtual environments creates a small layer on top of the main Python package, the overhead is minimal. Switching O3DE to use Python virtual environments will add the following benefits:

Feature design description:

This feature is a change to how O3DE uses Python, and the updates will have minimal impacts on development workflows that do not involve Python directly.

Technical design description:

Python 3rd Party Package A new revision of the Python 3rd Party package is not necessary. The Python virtual environment module is part of the standard Python library. The pip modules are already part of the package as well. The handling of the 3rd Party Package will change in the following ways:

Bootstrap Process

The initial bootstrap process for Python will be updated to include the creation of the Python virtual environment, and the subsequent O3DE specific installation of modules will use the venv instead of direct Python calls in the 3rd Party Package. The target location of the virtual environment will be unique to the engine path from where the bootstrap process is occuring. O3DE will use the full absolute path of the current engine to generate a reasonable unique identifier. This path will be deterministic based on the engine path.

Below is the current bootstrap flow for O3DE:

  1. Download and unpack the Python 3rd Party Package into $O3DE/Python/runtime if needed.
  2. Only perform the package validation on the first time download, not on subsequent cmake project generation calls. Prevent normal 3rd party package hash validation. This is due to the fact that the subsequent calls to pip into the 3rd Party package will alter the package contents, and thus the package hash will be different.
  3. Perform pip install using the O3DE Python script under $O3DE/Python to perform the installation of the following:
    • General packages defined in $O3DE/Python/requirements.txt
    • Packages from $O3DE/Tools/LyTestTools
    • Packages from $O3DE/Tools/RemoteConsole/ly_remote_console
    • Packages from $O3DE/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools

The updated bootstrap flow for O3DE will be:

  1. Download and unpack the Python 3rd Party Package into $LY_3RDPARTY_PATH if needed.

  2. Perform the standard package validation against the package hash.

  3. Calculate the full path ($PYTHON_VENV_PATH) to the Python virtual environment based on the absolute path of the engine. The full path will be: $HOME/.o3de/Python/$ENGINE_ID/ where $ENGINE_ID will be the first 8 hexadecimal digits of the SHA-1 hash of the absolute path of the current engine.

  4. Check if the $PYTHON_VENV_PATH path exists. If it exists, check the following scenarios:

    • Check if the expected Python binaries (by platform) exists.
    • Check if the expected pyvenv.cfg exists.
    • Check if a 3rd Party hash marker file .hash exists.
    • If the .hash exists, check if it matches the package hash of the 3rd Party Python package the virtual environment is meant for. If one or more of the above check fails, then check against a cmake variable O3DE_ERROR_ON_PYTHON_HASH_MISMATCH to determine if we want to clear out the venv and regenerate it, or just report a fatal error with instructions on how to re-generate the venv.
  5. If $PYTHON_VENV_PATH does not exist, or it was cleared out by the above validation checks, then perform the creation of the virtual environment based on the following command ($PYTHON_EXECUTABLE will be the Python executable inside the Python 3rd Party package, and its sub folder is platform specific. ) $PYTHON_EXECUTABLE -m venv $PYTHON_VENV_PATH Once the environment is initialized to $PYTHON_VENV_PATH, write the package hash for the Python 3rd Party package to the folder to .hash.

  6. Perform pip install using the O3DE Python script under $O3DE/Python to perform the installation of the following:

    • General packages defined in $O3DE/Python/requirements.txt
    • Packages from $O3DE/Tools/LyTestTools
    • Packages from $O3DE/Tools/RemoteConsole/ly_remote_console
    • Packages from $O3DE/AutomatedTesting/Gem/PythonTests/EditorPythonTestTools The O3DE Python will be updated to use the Python virtual environment for the engine instead (see 'Python script updates')

Python PAL-ification

The current Python bootstrap script does not employ the Platform Abstraction Layer (PAL) pattern for the Python packages since it is used in both generation and script modes. In script mode, the main LYPython.cmake does not have access to many of the PAL variables that is available during the generation mode, so it currently does a manual check against the current platform (and architecture) to determine the package name, hash, etc.

In order to refactor the script to follow the PAL pattern, the PAL variables that are needed will instead be initialized by the current get_python.* scripts instead. get_Python.bat is guaranteed to run on Windows only, so the windows specific PAL variables can be hardcoded in that script. get_Python.sh is valid for both Linux and Mac, so platform detection (as well as architecture detection for Linux) will be handled there. Since this is a BASH script, it can detect the platform by using the $OSTYPE environment variable.

These get_Python.* scripts subsequently calls the get_python.cmake, which provides the necessary PAL* related variables needed and passes it to LYPython.cmake to run through the same bootstrap process as a cmake project generation workflow.

The platform specific information for the Python package will be moved to PAL'ified files cmake/3rdParty/Platform/{$PLATFORM_NAME}/Python_{$PLATFORM_LOWER}.cmake.

Python Script Updates

Since PAL-ification is handled in the either the get_Python.* scripts or part of the cmake generation workflow, the location of the Python virtual environment for the engine will be set to a known location based on the current engine rather than trying to detect the platform and the location of the actual Python 3rd Party package. The current Python.cmd/Python.sh will updated to generate the deterministic path to the Python virtual environment $PYTHON_VENV_PATH by performing the same logic employed by the bootstrap workflow.

Instead of running Python from the 3rd Party package, it will instead run through the execution flow for virtual environments:

  1. Run the activate script within the venv to setup the proper environment
  2. Run the Python executable within the venv
  3. Run the deactivate script (Windows) to restore the environment

The activate/deactivate is necessary to set up and tear down the virtual environment. (Only activate is needed on BASH since it is running in its own shell)

Embedded Python Updates

The targets that depend on the Pybind 3rd Party Package (Project Manager and the Editor Python Bindings Gem) will also need to update its environment to use the virtual environment. The pyvenv.cfg file is only used when running the Python interpreter that is inside the virtual environment. With embedded Python, however, we will need to read in this file and set the PYTHON_HOME to the 3rd Party Python library. In addition to initializing the Python interpreter from pybind, we will need to scan the virtual environment's site-packages to look for *.egg-link files. These files tell the Python interpreter where to look for additional modules in other folders. Pybind11 has trouble interpreting these files, so we will need to work around this issue by scanning and manually adding the paths that are contained in these egg-link files into the `$PYTHON_PATH`` environment.

What are the advantages of the feature?

What are the disadvantages of the feature?

How will this be implemented or integrated into the O3DE environment?

The implementation is described in the technical design description above. For source versions of the engine, the updated scripts will perform the bootstrap process as needed to setup the Python environment properly. The legacy Python runtime will still exist in the engine path and may be removed manually.

Are there any alternatives to this feature?

How will users learn this feature?

The bootstrap process will occur automatically, and all Python calls in O3DE currently are wrapped with O3DE python script files already. Users will learn of this update through the release notes and impactful change messaging from O3DE.

Are there any open questions?

lemonade-dm commented 9 months ago

Currently the Python 3rd Party package is downloaded and extracted into $O3DE_HOME/Python/runtime/$PYTHON_PACKAGE_NAME. The location will change to $LY_3RDPARTY_PATH (inside of the $HOME/.o3de/%USERPROFILE%.o3de folder)

The variable placeholder of $O3DE_HOME is ambiguous in meaning to a reader. It could mean the the root of the O3DE user directory inside the user home directory (i.e ~/.o3de). However in the case that it is being used in, the meaning is the O3DE engine root.

Check if the $PYTHON_VENV_PATH path exists. If it exists, check the following scenarios:

Check if the expected Python binaries (by platform) exists. Check if the expected pyvenv.cfg exists. Check if a 3rd Party hash marker file .hash exists. If the .hash exists, check if it matches the package hash of the 3rd Party Python package the virtual environment is meant for. If one or more of the above check fails, then clear out the folder.

I think there should be a CMake option to choose to not clear out the 3rd Party folder if the hash file doesn't match the 3rd Party Package hash file. Instead a FATAL_ERROR could occur in that case. Perhaps an option such as, O3DE_ERROR_ON_PYTHON_HASH_MISMATCH

Pip Install of the o3de CLI python package

This RFC should mention that the o3de CLI package is also pip installed locally and that would be needed to allow methods such as import o3de to still function: https://github.com/o3de/o3de/blob/development/cmake/LYPython.cmake#L304

spham-amzn commented 9 months ago

Check if the $PYTHON_VENV_PATH path exists. If it exists, check the following scenarios:

Check if the expected Python binaries (by platform) exists. Check if the expected pyvenv.cfg exists. Check if a 3rd Party hash marker file .hash exists. If the .hash exists, check if it matches the package hash of the 3rd Party Python package the virtual environment is meant for. If one or more of the above check fails, then clear out the folder.

I think there should be a CMake option to choose to not clear out the 3rd Party folder if the hash file doesn't match the 3rd Party Package hash file. Instead a FATAL_ERROR could occur in that case. Perhaps an option such as, O3DE_ERROR_ON_PYTHON_HASH_MISMATCH

Pip Install of the o3de CLI python package

This RFC should mention that the o3de CLI package is also pip installed locally and that would be needed to allow methods such as import o3de to still function: https://github.com/o3de/o3de/blob/development/cmake/LYPython.cmake#L304

Actually, I think by default it should report the detailed error, which will also suggest to re-run the a 'Force Update' type of argument if ran through get_python, or set a O3DE_RESET_ENV_ON_PYTHON_UPDATES (which is a variable that is set from the get_python flow)

lemonade-dm commented 9 months ago

We should keep this open for comment for the next two weeks until December 20th

AMZN-alexpete commented 8 months ago

Do you think we could/should move the $O3DE/python folder? Could we move what remains into $O3DE/scripts or $O3DE/Tools/python ? Any idea why it was a root level folder initially? There would be a concern of breaking backwards compatibility for anyone who had scripts that referenced $O3DE/Python/Python.cmd or Python.sh but I like the idea of making the O3DE root as simple/efficient as possible.

spham-amzn commented 8 months ago

Right now, the venv folder that is created is meant for the engine. New projects will share the same virtual environment as the engine. How important is it to have a venv at the project level as well? That will at least protect individual projects from engine-wide python environment or librarie updates? At it fits the pattern for virtual environments better.

spham-amzn commented 6 months ago

Feature has been implemented and merged in https://github.com/o3de/o3de/commit/cd07167884f06e7813a2a5f7175c45360a56cef8