# coding:utf-8
#!/usr/bin/python
#
# 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
#
#
# -------------------------------------------------------------------------

"""! @brief
Module Documentation:
    < DCCsi > / foundation.py

Running this module installs the DCCsi python requirements.txt for other python interpreters (like Maya)

It installs based on the python version into a location (such as):
<o3de>/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/3rdParty/Python/Lib/3.x

This is to ensure that we are not modifying the users DCC tools install directly.

For this script to function on windows you may need Administrator privledges.
^ You only have to start with Admin rights if you are running foundation.py or otherwise updating packages

Open an admin elevated cmd prompt here:
C:\depot\o3de-dev\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface

The following would execpt this script, the default behaviour is to check
the o3de python and install the requirements.txt for that python version,

  >python.cmd foundation.py

To Do: document additional usage (how to install for Maya 2022 py3.7, etc.)
"""
# -------------------------------------------------------------------------
# standard imports
import subprocess
import sys
import os
import site
import timeit
import inspect
import traceback
from pathlib import Path
import logging as _logging
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
#os.environ['PYTHONINSPECT'] = 'True'
_START = timeit.default_timer() # start tracking

# global scope
_MODULENAME = 'foundation'
_LOGGER = _logging.getLogger(_MODULENAME)
_LOGGER.debug('Initializing: {}.'.format({_MODULENAME}))
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
# Local access
_MODULE_PATH = Path(__file__)            # this script
_PATH_DCCSIG = Path(_MODULE_PATH.parent) # dccsi
os.environ['PATH_DCCSIG'] = _PATH_DCCSIG.as_posix()
site.addsitedir(_PATH_DCCSIG.as_posix()) # python path
os.chdir(_PATH_DCCSIG.as_posix())

# the path we want to install packages into
STR_PATH_DCCSI_PYTHON_LIB = str('{0}\\3rdParty\\Python\\Lib\\{1}.x\\{1}.{2}.x\\site-packages')

# these are just defaults and are meant to be replaced by info for the target python.exe
_SYS_VER_MAJOR = sys.version_info.major
_SYS_VER_MINOR = sys.version_info.minor
# the default will be based on the python executable running this module
# this value should be replaced with the sys,version of the target python
# for example mayapy, or blenders python, etc.
_PATH_DCCSI_PYTHON_LIB = Path(STR_PATH_DCCSI_PYTHON_LIB.format(_PATH_DCCSIG,
                                                               _SYS_VER_MAJOR,
                                                               _SYS_VER_MINOR))

# this is the shared default requirements.txt file to install for python 3.6.x+
_DCCSI_PYTHON_REQUIREMENTS = Path(_PATH_DCCSIG, 'requirements.txt')

# this will default to the python interpretter running this script (probably o3de)
# this should be relaced by the target interpretter python exe, like mayapy.exe
_PYTHON_EXE = Path(sys.executable)
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
def check_pip(python_exe=_PYTHON_EXE):
    """Check if pip is installed and log what version"""

    python_exe = Path(python_exe)

    if python_exe.exists():
        result = subprocess.call( [python_exe.as_posix(), "-m", "pip", "--version"] )
        _LOGGER.info(f'foundation.check_pip(), result: {result}')
        return result
    else:
        _LOGGER.error(f'python_exe does not exist: {python_exe}')
        return 1
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
def ensurepip(python_exe=_PYTHON_EXE, upgrade=False):
    """Will use ensurepip method to ensure pip is installed"""

    #note: this doesn't work with python 3.7 which is the version o3de is on
    #luckily o3de comes with working pip
    #if this errors out with an exception and "ValueError: bad marshal data (unknown type code)"
    #you should try to install pip using dfoundation.install_pip() method

    result = 0

    python_exe = Path(python_exe)

    if python_exe.exists():

        if upgrade:
            result = subprocess.call( [python_exe.as_posix(), "-m", "ensurepip", "--upgrade"] )
            _LOGGER.info(f'foundation.ensurepip(python_exe, upgrade=True), result: {result}')
        else:
            result = subprocess.call( [python_exe.as_posix(), "-m", "ensurepip"] )
            _LOGGER.info(f'foundation.ensurepip(python_exe), result: {result}')
    else:
        _LOGGER.error(f'python_exe does not exist: {python_exe}')
        return 0

    return result
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
_GET_PIP_PY37_URL = "https://bootstrap.pypa.io/get-pip.py"
_GET_PIP_PY27_URL = "https://bootstrap.pypa.io/pip/2.7/get-pip.py"

# version to download (DL)
if sys.version_info.major >= 3 and sys.version_info.minor >= 7:
    DL_URL = _GET_PIP_PY37_URL
elif sys.version_info.major < 3:
    DL_URL = _GET_PIP_PY27_URL

# temp dir to store in:
_PIP_DL_LOC = Path(_PATH_DCCSIG) / '__tmp__'
if not _PIP_DL_LOC.exists():
    try:
        _PIP_DL_LOC.mkdir(parents=True)
    except Exception as e:
        _LOGGER.error(f'error: {e}, could not .mkdir(): {PIP_DL_LOC.as_posix()}')

# default file location to store it:
_PIP_DL_LOC = _PIP_DL_LOC / 'get-pip.py'
try:
    _PIP_DL_LOC.touch(mode=0o666, exist_ok=True)
except Exception as e:
    _LOGGER.error(f'error: {e}, could not .touch(): {PIP_DL_LOC.as_posix()}')

def download_getpip(url=DL_URL, file_store=_PIP_DL_LOC):
    """Attempts to download the get-pip.py script"""
    import requests

    # ensure what is passed in is a Path object
    file_store = Path(file_store)
    file_store = Path.joinpath(file_store)

    try:
        file_store.exists()
    except FileExistsError as e:
        try:
            file_store.touch()
        except FileExistsError as e:
            _LOGGER.error(f'Could not make file: {file_store}')

    try:
        _get_pip = requests.get(url)
    except Exception as e:
        _LOGGER.error(f'could not request: {url}')

    try:
        file = open(file_store.as_posix(), 'wb').write(_get_pip.content)
        return file
    except IOError as e:
        _LOGGER.error(f'could not write: {file_store.as_posix()}')
        return None
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
def install_pip(python_exe=_PYTHON_EXE, download=True, upgrade=True, getpip=_PIP_DL_LOC):
    """Installs pip via get-pip.py"""
    result = 0

    if download:
        getpip = download_getpip()
        if not getpip:
            return result

    python_exe = Path(python_exe)

    if python_exe.exists():
        python_exe = python_exe.as_posix()
        result = subprocess.call( [python_exe, "-m", getpip] )
        _LOGGER.info(f'result: {result}')
    else:
        _LOGGER.error(f'python_exe does not exist: {python_exe}')
        return 0

    if upgrade:
        python_exe = python_exe.as_posix()
        result = subprocess.call( [python_exe, "-m", "pip", "install", "--upgrade", "pip"] )
        _LOGGER.info(f'result: {result}')
        return result

    return result
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
# version of requirements.txt to installa
if sys.version_info.major >= 3 and sys.version_info.minor >= 7:
    _REQUIREMENTS = _DCCSI_PYTHON_REQUIREMENTS
elif sys.version_info.major == 2 and sys.version_info.minor >= 7:
    _LOGGER.warning('Python 2.7 is end of life, we recommend using tools that operate py3.7 or higher')
    _REQUIREMENTS = Path(_PATH_DCCSIG,
                         'Tools',
                         'Resources',
                         'py27',
                         'requirements.txt').as_posix()
else:
    _REQUIREMENTS = None
    _LOGGER.error(f'Unsupported version: {sys.version_info}')

def install_requirements(python_exe=_PYTHON_EXE,
                         requirements=_REQUIREMENTS,
                         target_loc=_PATH_DCCSI_PYTHON_LIB.as_posix()):
    """Installs the DCCsi requirments.txt"""

    python_exe = Path(python_exe)
    requirements = Path(requirements)
    target_loc = Path(target_loc)

    if python_exe.exists():
        ## install required packages
        inst_cmd = [python_exe.as_posix(), "-m", "pip", "install",
                    "-r", requirements.as_posix(), "-t", target_loc.as_posix()]
        result = subprocess.call( inst_cmd )
        return result
    else:
        _LOGGER.error(f'python_exe does not exist: {python_exe}')
        return 0
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
def install_pkg(python_exe=_PYTHON_EXE,
                pkg_name='pathlib',
                target_loc=_PATH_DCCSI_PYTHON_LIB.as_posix()):
    """Installs a pkg for DCCsi"""

    python_exe = Path(python_exe)
    pkg_name = Path(pkg_name)
    target_loc = Path(target_loc)

    if python_exe.exists():
        inst_cmd = [python_exe.as_posix(), "-m", "pip", "install", pkg_name.as_posix(),
                    "-t", target_loc.as_posix()]
        result = subprocess.call( inst_cmd )
        return result
    else:
        _LOGGER.error(f'python_exe does not exist: {python_exe}')
        return 0
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
def run_command() -> 'subprocess.CompletedProcess[str]':
    """Run some subprocess that captures output as ``str``"""
    return subprocess.CompletedProcess(args=[], returncode=0, stdout='')
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
def arg_bool(bool_arg, desc='arg desc not set'):
    """cast a arg bool to a python bool"""

    _LOGGER.info(f"Checking '{desc}': {bool_arg}")

    if bool_arg in ('True', 'true', '1'):
        return True
    elif bool_arg in ('False', 'false', '0'):
        return False
    else:
        return bool_arg
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
def set_version(ver_major=sys.version_info.major, ver_minor=sys.version_info.minor):
    global _SYS_VER_MAJOR
    global _SYS_VER_MINOR
    global _PATH_DCCSI_PYTHON_LIB

    _SYS_VER_MAJOR = ver_major
    _SYS_VER_MINOR = ver_minor
    _PATH_DCCSI_PYTHON_LIB = Path(STR_PATH_DCCSI_PYTHON_LIB.format(_PATH_DCCSIG,
                                                                   _SYS_VER_MAJOR,
                                                                   _SYS_VER_MINOR))
    return _PATH_DCCSI_PYTHON_LIB
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
def get_version(_PYTHON_EXE):
    _PYTHON_EXE = Path(_PYTHON_EXE)
    if _PYTHON_EXE.exists():
        # this will switch to run the specified dcc tools python exe and determine version
        _COMMAND = [_PYTHON_EXE.as_posix(), "--version"]
        _process = subprocess.Popen(_COMMAND, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        _out, _err = _process.communicate()
        _out = _out.decode("utf-8")  # decodes byte string to string
        _out = _out.replace("\r\n", "")  # clean
        _LOGGER.info(f'Python Version is: {_out}')
        _ver = _out.split(" ")[-1]  # split by space, take version
        _ver = _ver.split('.')  # splity by . to list
        return _ver
    else:
        _LOGGER.error(f'Python exe does not exist: {_PYTHON_EXE.as_posix()}')
        return None
# -------------------------------------------------------------------------


###########################################################################
# Main Code Block, runs this script as main (testing)
# -------------------------------------------------------------------------
if __name__ == '__main__':
    """Run this file as main (external commandline)"""

    #os.environ['PYTHONINSPECT'] = 'True'

    STR_CROSSBAR = f"{'-' * 74}"

    _DCCSI_GDEBUG = False
    _DCCSI_DEV_MODE = False

    # default loglevel to info unless set
    _DCCSI_LOGLEVEL = _logging.INFO

    if _DCCSI_GDEBUG:
        # override loglevel if runnign debug
        _DCCSI_LOGLEVEL = _logging.DEBUG

    FRMT_LOG_LONG = "[%(name)s][%(levelname)s] >> %(message)s (%(asctime)s; %(filename)s:%(lineno)d)"

    # configure basic logger
    # note: not using a common logger to reduce cyclical imports
    _logging.basicConfig(level=_DCCSI_LOGLEVEL,
                         format=FRMT_LOG_LONG,
                        datefmt='%m-%d %H:%M')

    _LOGGER = _logging.getLogger(_MODULENAME)

    _LOGGER.info(STR_CROSSBAR)
    _LOGGER.debug('Initializing: {}.'.format({_MODULENAME}))
    _LOGGER.debug('_DCCSI_GDEBUG: {}'.format(_DCCSI_GDEBUG))
    _LOGGER.debug('_DCCSI_DEV_MODE: {}'.format(_DCCSI_DEV_MODE))
    _LOGGER.debug('_DCCSI_LOGLEVEL: {}'.format(_DCCSI_LOGLEVEL))

    import argparse

    parser = argparse.ArgumentParser(
        description='O3DE DCCsi Setup (aka Foundation). Will install DCCsi python package dependancies, for various DCC tools.',
            epilog="It is suggested to use '-py' or '--python_exe' to pass in the python exe for the target dcc tool.")

    parser.add_argument('-gd', '--global-debug',
                        type=bool,
                        required=False,
                        help='Enables global debug flag.')

    parser.add_argument('-dm', '--developer-mode',
                        type=bool,
                        required=False,
                        default=False,
                        help='(NOT IMPLEMENTED) Enables dev mode for early auto attaching debugger.')

    parser.add_argument('-sd', '--set-debugger',
                        type=str,
                        required=False,
                        default='WING',
                        help='(NOT IMPLEMENTED) Default debugger: WING, others: PYCHARM and VSCODE.')

    parser.add_argument('-py', '--python_exe',
                        type=str,
                        required=False,
                        help='The python interpretter you want to run in the subprocess')

    parser.add_argument('-cp', '--check_pip',
                        required=False,
                        default=True,
                        help='Checks for pip')

    parser.add_argument('-ep', '--ensurepip',
                        required=False,
                        default=False,
                        help='Uses ensurepip, to make sure pip is installed')

    parser.add_argument('-ip', '--install_pip',
                        required=False,
                        default=False,
                        help='Attempts install pip via download of get-pip.py')

    parser.add_argument('-ir', '--install_requirements',
                        required=False,
                        default=True,
                        help='Exits python')

    parser.add_argument('-ex', '--exit',
                        type=bool,
                        required=False,
                        default=False,
                        help='Exits python. Do not exit if you want to be in interactive interpretter after config')

    args = parser.parse_args()

    # easy overrides
    if args.global_debug:
        _DCCSI_GDEBUG = True
        os.environ["DYNACONF_DCCSI_GDEBUG"] = str(_DCCSI_GDEBUG)

    if not args.python_exe:
        _LOGGER.warning("It is suggested to use arg '-py' or '--python_exe' to pass in the python exe for the target dcc tool.")

    if args.python_exe:
        _PYTHON_EXE = Path(args.python_exe)
        _LOGGER.info(f'Target py exe is: {_PYTHON_EXE}')

        if _PYTHON_EXE.exists():
            _py_version = get_version(_PYTHON_EXE)

            # then we can change the version dependant target folder for pkg install
            _PATH_DCCSI_PYTHON_LIB = set_version(_py_version[0], _py_version[1])

            if _PATH_DCCSI_PYTHON_LIB.exists():
                _LOGGER.info(f'Requirements, install target: {_PATH_DCCSI_PYTHON_LIB}')
            else:
                _PATH_DCCSI_PYTHON_LIB.touch()
                _LOGGER.info(f'.touch(): {_PATH_DCCSI_PYTHON_LIB}')
        else:
            _LOGGER.error(f'This py exe does not exist:{_PYTHON_EXE}')
            sys.exit()

    # this will verify pip is installed for the target python interpretter/env
    if arg_bool(args.check_pip, desc='args.check_pip'):
        _LOGGER.info(f'calling foundation.check_pip()')
        result = check_pip(_PYTHON_EXE)

        if result != 0:
            _LOGGER.warning( f'check_pip(), Invalid result: { result }' )

    if arg_bool(args.ensurepip, desc='args.ensurepip'):
        _LOGGER.info(f'calling foundation.ensurepip()')
        ensurepip(_PYTHON_EXE)

    if arg_bool(args.install_pip, desc='args.install_pip'):
        _LOGGER.info(f'calling foundation.install_pip()')
        install_pip(_PYTHON_EXE)

    # installing the requirments.txt is enabled by default
    if arg_bool(args.install_requirements, desc='args.check_pip'):
        _LOGGER.info(f'calling foundation.install_requirements( {_PYTHON_EXE}, target_loc = {_PATH_DCCSI_PYTHON_LIB.as_posix()} )')
        install_requirements(_PYTHON_EXE, target_loc = _PATH_DCCSI_PYTHON_LIB.as_posix())

    # -- DONE ----
    _LOGGER.info(STR_CROSSBAR)

    _LOGGER.info('O3DE DCCsi {0}.py took: {1} sec'.format(_MODULENAME, timeit.default_timer() - _START))

    if args.exit:
        import sys
        # return
        sys.exit()
    else:
        # custom prompt
        sys.ps1 = "[{}]>>".format(_MODULENAME)
# --- END -----------------------------------------------------------------

# coding:utf-8
#!/usr/bin/python
#
# 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
#
#
# -------------------------------------------------------------------------

"""! @brief
Module Documentation:
    < DCCsi > / foundation.py

Running this module installs the DCCsi python requirements.txt for other python interpreters (like Maya)

It installs based on the python version into a location (such as):
<o3de>/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/3rdParty/Python/Lib/3.x

This is to ensure that we are not modifying the users DCC tools install directly.

For this script to function on windows you may need Administrator privledges.
^ You only have to start with Admin rights if you are running foundation.py or otherwise updating packages

Open an admin elevated cmd prompt here:
C:\depot\o3de-dev\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface

The following would execpt this script, the default behaviour is to check
the o3de python and install the requirements.txt for that python version,

  >python.cmd foundation.py

To Do: document additional usage (how to install for Maya 2022 py3.7, etc.)
"""
# -------------------------------------------------------------------------
# standard imports
import subprocess
import sys
import os
import site
import timeit
import inspect
import traceback
from pathlib import Path
import logging as _logging
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
#os.environ['PYTHONINSPECT'] = 'True'
_START = timeit.default_timer() # start tracking

# global scope
_MODULENAME = 'foundation'
_LOGGER = _logging.getLogger(_MODULENAME)
_LOGGER.debug('Initializing: {}.'.format({_MODULENAME}))
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
# Local access
_MODULE_PATH = Path(__file__)            # this script
_PATH_DCCSIG = Path(_MODULE_PATH.parent) # dccsi
os.environ['PATH_DCCSIG'] = _PATH_DCCSIG.as_posix()
site.addsitedir(_PATH_DCCSIG.as_posix()) # python path
os.chdir(_PATH_DCCSIG.as_posix())

# the path we want to install packages into
STR_PATH_DCCSI_PYTHON_LIB = str('{0}\\3rdParty\\Python\\Lib\\{1}.x\\{1}.{2}.x\\site-packages')

# these are just defaults and are meant to be replaced by info for the target python.exe
_SYS_VER_MAJOR = sys.version_info.major
_SYS_VER_MINOR = sys.version_info.minor
# the default will be based on the python executable running this module
# this value should be replaced with the sys,version of the target python
# for example mayapy, or blenders python, etc.
_PATH_DCCSI_PYTHON_LIB = Path(STR_PATH_DCCSI_PYTHON_LIB.format(_PATH_DCCSIG,
                                                               _SYS_VER_MAJOR,
                                                               _SYS_VER_MINOR))

# this is the shared default requirements.txt file to install for python 3.6.x+
_DCCSI_PYTHON_REQUIREMENTS = Path(_PATH_DCCSIG, 'requirements.txt')

# this will default to the python interpretter running this script (probably o3de)
# this should be relaced by the target interpretter python exe, like mayapy.exe
_PYTHON_EXE = Path(sys.executable)
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
def check_pip(python_exe=_PYTHON_EXE):
    """Check if pip is installed and log what version"""

    python_exe = Path(python_exe)

    if python_exe.exists():
        result = subprocess.call( [python_exe.as_posix(), "-m", "pip", "--version"] )
        _LOGGER.info(f'foundation.check_pip(), result: {result}')
        return result
    else:
        _LOGGER.error(f'python_exe does not exist: {python_exe}')
        return 1
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
def ensurepip(python_exe=_PYTHON_EXE, upgrade=False):
    """Will use ensurepip method to ensure pip is installed"""

    #note: this doesn't work with python 3.7 which is the version o3de is on
    #luckily o3de comes with working pip
    #if this errors out with an exception and "ValueError: bad marshal data (unknown type code)"
    #you should try to install pip using dfoundation.install_pip() method

    result = 0

    python_exe = Path(python_exe)

    if python_exe.exists():

        if upgrade:
            result = subprocess.call( [python_exe.as_posix(), "-m", "ensurepip", "--upgrade"] )
            _LOGGER.info(f'foundation.ensurepip(python_exe, upgrade=True), result: {result}')
        else:
            result = subprocess.call( [python_exe.as_posix(), "-m", "ensurepip"] )
            _LOGGER.info(f'foundation.ensurepip(python_exe), result: {result}')
    else:
        _LOGGER.error(f'python_exe does not exist: {python_exe}')
        return 0

    return result
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
_GET_PIP_PY37_URL = "https://bootstrap.pypa.io/get-pip.py"
_GET_PIP_PY27_URL = "https://bootstrap.pypa.io/pip/2.7/get-pip.py"

# version to download (DL)
if sys.version_info.major >= 3 and sys.version_info.minor >= 7:
    DL_URL = _GET_PIP_PY37_URL
elif sys.version_info.major < 3:
    DL_URL = _GET_PIP_PY27_URL

# temp dir to store in:
_PIP_DL_LOC = Path(_PATH_DCCSIG) / '__tmp__'
if not _PIP_DL_LOC.exists():
    try:
        _PIP_DL_LOC.mkdir(parents=True)
    except Exception as e:
        _LOGGER.error(f'error: {e}, could not .mkdir(): {PIP_DL_LOC.as_posix()}')

# default file location to store it:
_PIP_DL_LOC = _PIP_DL_LOC / 'get-pip.py'
try:
    _PIP_DL_LOC.touch(mode=0o666, exist_ok=True)
except Exception as e:
    _LOGGER.error(f'error: {e}, could not .touch(): {PIP_DL_LOC.as_posix()}')

def download_getpip(url=DL_URL, file_store=_PIP_DL_LOC):
    """Attempts to download the get-pip.py script"""
    import requests

    # ensure what is passed in is a Path object
    file_store = Path(file_store)
    file_store = Path.joinpath(file_store)

    try:
        file_store.exists()
    except FileExistsError as e:
        try:
            file_store.touch()
        except FileExistsError as e:
            _LOGGER.error(f'Could not make file: {file_store}')

    try:
        _get_pip = requests.get(url)
    except Exception as e:
        _LOGGER.error(f'could not request: {url}')

    try:
        file = open(file_store.as_posix(), 'wb').write(_get_pip.content)
        return file
    except IOError as e:
        _LOGGER.error(f'could not write: {file_store.as_posix()}')
        return None
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
def install_pip(python_exe=_PYTHON_EXE, download=True, upgrade=True, getpip=_PIP_DL_LOC):
    """Installs pip via get-pip.py"""
    result = 0

    if download:
        getpip = download_getpip()
        if not getpip:
            return result

    python_exe = Path(python_exe)

    if python_exe.exists():
        python_exe = python_exe.as_posix()
        result = subprocess.call( [python_exe, "-m", getpip] )
        _LOGGER.info(f'result: {result}')
    else:
        _LOGGER.error(f'python_exe does not exist: {python_exe}')
        return 0

    if upgrade:
        python_exe = python_exe.as_posix()
        result = subprocess.call( [python_exe, "-m", "pip", "install", "--upgrade", "pip"] )
        _LOGGER.info(f'result: {result}')
        return result

    return result
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
# version of requirements.txt to installa
if sys.version_info.major >= 3 and sys.version_info.minor >= 7:
    _REQUIREMENTS = _DCCSI_PYTHON_REQUIREMENTS
elif sys.version_info.major == 2 and sys.version_info.minor >= 7:
    _LOGGER.warning('Python 2.7 is end of life, we recommend using tools that operate py3.7 or higher')
    _REQUIREMENTS = Path(_PATH_DCCSIG,
                         'Tools',
                         'Resources',
                         'py27',
                         'requirements.txt').as_posix()
else:
    _REQUIREMENTS = None
    _LOGGER.error(f'Unsupported version: {sys.version_info}')

def install_requirements(python_exe=_PYTHON_EXE,
                         requirements=_REQUIREMENTS,
                         target_loc=_PATH_DCCSI_PYTHON_LIB.as_posix()):
    """Installs the DCCsi requirments.txt"""

    python_exe = Path(python_exe)
    requirements = Path(requirements)
    target_loc = Path(target_loc)

    if python_exe.exists():
        ## install required packages
        inst_cmd = [python_exe.as_posix(), "-m", "pip", "install",
                    "-r", requirements.as_posix(), "-t", target_loc.as_posix()]
        result = subprocess.call( inst_cmd )
        return result
    else:
        _LOGGER.error(f'python_exe does not exist: {python_exe}')
        return 0
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
def install_pkg(python_exe=_PYTHON_EXE,
                pkg_name='pathlib',
                target_loc=_PATH_DCCSI_PYTHON_LIB.as_posix()):
    """Installs a pkg for DCCsi"""

    python_exe = Path(python_exe)
    pkg_name = Path(pkg_name)
    target_loc = Path(target_loc)

    if python_exe.exists():
        inst_cmd = [python_exe.as_posix(), "-m", "pip", "install", pkg_name.as_posix(),
                    "-t", target_loc.as_posix()]
        result = subprocess.call( inst_cmd )
        return result
    else:
        _LOGGER.error(f'python_exe does not exist: {python_exe}')
        return 0
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
def run_command() -> 'subprocess.CompletedProcess[str]':
    """Run some subprocess that captures output as ``str``"""
    return subprocess.CompletedProcess(args=[], returncode=0, stdout='')
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
def arg_bool(bool_arg, desc='arg desc not set'):
    """cast a arg bool to a python bool"""

    _LOGGER.info(f"Checking '{desc}': {bool_arg}")

    if bool_arg in ('True', 'true', '1'):
        return True
    elif bool_arg in ('False', 'false', '0'):
        return False
    else:
        return bool_arg
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
def set_version(ver_major=sys.version_info.major, ver_minor=sys.version_info.minor):
    global _SYS_VER_MAJOR
    global _SYS_VER_MINOR
    global _PATH_DCCSI_PYTHON_LIB

    _SYS_VER_MAJOR = ver_major
    _SYS_VER_MINOR = ver_minor
    _PATH_DCCSI_PYTHON_LIB = Path(STR_PATH_DCCSI_PYTHON_LIB.format(_PATH_DCCSIG,
                                                                   _SYS_VER_MAJOR,
                                                                   _SYS_VER_MINOR))
    return _PATH_DCCSI_PYTHON_LIB
# -------------------------------------------------------------------------


# -------------------------------------------------------------------------
def get_version(_PYTHON_EXE):
    _PYTHON_EXE = Path(_PYTHON_EXE)
    if _PYTHON_EXE.exists():
        # this will switch to run the specified dcc tools python exe and determine version
        _COMMAND = [_PYTHON_EXE.as_posix(), "--version"]
        _process = subprocess.Popen(_COMMAND, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        _out, _err = _process.communicate()
        _out = _out.decode("utf-8")  # decodes byte string to string
        _out = _out.replace("\r\n", "")  # clean
        _LOGGER.info(f'Python Version is: {_out}')
        _ver = _out.split(" ")[-1]  # split by space, take version
        _ver = _ver.split('.')  # splity by . to list
        return _ver
    else:
        _LOGGER.error(f'Python exe does not exist: {_PYTHON_EXE.as_posix()}')
        return None
# -------------------------------------------------------------------------


###########################################################################
# Main Code Block, runs this script as main (testing)
# -------------------------------------------------------------------------
if __name__ == '__main__':
    """Run this file as main (external commandline)"""

    #os.environ['PYTHONINSPECT'] = 'True'

    STR_CROSSBAR = f"{'-' * 74}"

    _DCCSI_GDEBUG = False
    _DCCSI_DEV_MODE = False

    # default loglevel to info unless set
    _DCCSI_LOGLEVEL = _logging.INFO

    if _DCCSI_GDEBUG:
        # override loglevel if runnign debug
        _DCCSI_LOGLEVEL = _logging.DEBUG

    FRMT_LOG_LONG = "[%(name)s][%(levelname)s] >> %(message)s (%(asctime)s; %(filename)s:%(lineno)d)"

    # configure basic logger
    # note: not using a common logger to reduce cyclical imports
    _logging.basicConfig(level=_DCCSI_LOGLEVEL,
                         format=FRMT_LOG_LONG,
                        datefmt='%m-%d %H:%M')

    _LOGGER = _logging.getLogger(_MODULENAME)

    _LOGGER.info(STR_CROSSBAR)
    _LOGGER.debug('Initializing: {}.'.format({_MODULENAME}))
    _LOGGER.debug('_DCCSI_GDEBUG: {}'.format(_DCCSI_GDEBUG))
    _LOGGER.debug('_DCCSI_DEV_MODE: {}'.format(_DCCSI_DEV_MODE))
    _LOGGER.debug('_DCCSI_LOGLEVEL: {}'.format(_DCCSI_LOGLEVEL))

    import argparse

    parser = argparse.ArgumentParser(
        description='O3DE DCCsi Setup (aka Foundation). Will install DCCsi python package dependancies, for various DCC tools.',
            epilog="It is suggested to use '-py' or '--python_exe' to pass in the python exe for the target dcc tool.")

    parser.add_argument('-gd', '--global-debug',
                        type=bool,
                        required=False,
                        help='Enables global debug flag.')

    parser.add_argument('-dm', '--developer-mode',
                        type=bool,
                        required=False,
                        default=False,
                        help='(NOT IMPLEMENTED) Enables dev mode for early auto attaching debugger.')

    parser.add_argument('-sd', '--set-debugger',
                        type=str,
                        required=False,
                        default='WING',
                        help='(NOT IMPLEMENTED) Default debugger: WING, others: PYCHARM and VSCODE.')

    parser.add_argument('-py', '--python_exe',
                        type=str,
                        required=False,
                        help='The python interpretter you want to run in the subprocess')

    parser.add_argument('-cp', '--check_pip',
                        required=False,
                        default=True,
                        help='Checks for pip')

    parser.add_argument('-ep', '--ensurepip',
                        required=False,
                        default=False,
                        help='Uses ensurepip, to make sure pip is installed')

    parser.add_argument('-ip', '--install_pip',
                        required=False,
                        default=False,
                        help='Attempts install pip via download of get-pip.py')

    parser.add_argument('-ir', '--install_requirements',
                        required=False,
                        default=True,
                        help='Exits python')

    parser.add_argument('-ex', '--exit',
                        type=bool,
                        required=False,
                        default=False,
                        help='Exits python. Do not exit if you want to be in interactive interpretter after config')

    args = parser.parse_args()

    # easy overrides
    if args.global_debug:
        _DCCSI_GDEBUG = True
        os.environ["DYNACONF_DCCSI_GDEBUG"] = str(_DCCSI_GDEBUG)

    if not args.python_exe:
        _LOGGER.warning("It is suggested to use arg '-py' or '--python_exe' to pass in the python exe for the target dcc tool.")

    if args.python_exe:
        _PYTHON_EXE = Path(args.python_exe)
        _LOGGER.info(f'Target py exe is: {_PYTHON_EXE}')

        if _PYTHON_EXE.exists():
            _py_version = get_version(_PYTHON_EXE)

            # then we can change the version dependant target folder for pkg install
            _PATH_DCCSI_PYTHON_LIB = set_version(_py_version[0], _py_version[1])

            if _PATH_DCCSI_PYTHON_LIB.exists():
                _LOGGER.info(f'Requirements, install target: {_PATH_DCCSI_PYTHON_LIB}')
            else:
                _PATH_DCCSI_PYTHON_LIB.touch()
                _LOGGER.info(f'.touch(): {_PATH_DCCSI_PYTHON_LIB}')
        else:
            _LOGGER.error(f'This py exe does not exist:{_PYTHON_EXE}')
            sys.exit()

    # this will verify pip is installed for the target python interpretter/env
    if arg_bool(args.check_pip, desc='args.check_pip'):
        _LOGGER.info(f'calling foundation.check_pip()')
        result = check_pip(_PYTHON_EXE)

        if result != 0:
            _LOGGER.warning( f'check_pip(), Invalid result: { result }' )

    if arg_bool(args.ensurepip, desc='args.ensurepip'):
        _LOGGER.info(f'calling foundation.ensurepip()')
        ensurepip(_PYTHON_EXE)

    if arg_bool(args.install_pip, desc='args.install_pip'):
        _LOGGER.info(f'calling foundation.install_pip()')
        install_pip(_PYTHON_EXE)

    # installing the requirments.txt is enabled by default
    if arg_bool(args.install_requirements, desc='args.check_pip'):
        _LOGGER.info(f'calling foundation.install_requirements( {_PYTHON_EXE}, target_loc = {_PATH_DCCSI_PYTHON_LIB.as_posix()} )')
        install_requirements(_PYTHON_EXE, target_loc = _PATH_DCCSI_PYTHON_LIB.as_posix())

    # -- DONE ----
    _LOGGER.info(STR_CROSSBAR)

    _LOGGER.info('O3DE DCCsi {0}.py took: {1} sec'.format(_MODULENAME, timeit.default_timer() - _START))

    if args.exit:
        import sys
        # return
        sys.exit()
    else:
        # custom prompt
        sys.ps1 = "[{}]>>".format(_MODULENAME)
# --- END -----------------------------------------------------------------

# coding:utf-8 #!/usr/bin/python # # 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 # # # ------------------------------------------------------------------------- ! @brief Module Documentation:     < DCCsi > / foundation.py  Running this module installs the DCCsi python requirements.txt for other python interpreters (like Maya)  It installs based on the python version into a location (such as): <o3de>/Gems/AtomLyIntegration/TechnicalArt/DccScriptingInterface/3rdParty/Python/Lib/3.x  This is to ensure that we are not modifying the users DCC tools install directly.  For this script to function on windows you may need Administrator privledges. ^ You only have to start with Admin rights if you are running foundation.py or otherwise updating packages  Open an admin elevated cmd prompt here: C:\depot\o3de-dev\Gems\AtomLyIntegration\TechnicalArt\DccScriptingInterface  The following would execpt this script, the default behaviour is to check the o3de python and install the requirements.txt for that python version,    >python.cmd foundation.py  To Do: document additional usage (how to install for Maya 2022 py3.7, etc.) # ------------------------------------------------------------------------- # standard imports # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- #os.environ['PYTHONINSPECT'] = 'True' # start tracking # global scope # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- # Local access # this script # dccsi # python path # the path we want to install packages into # these are just defaults and are meant to be replaced by info for the target python.exe # the default will be based on the python executable running this module # this value should be replaced with the sys,version of the target python # for example mayapy, or blenders python, etc. # this is the shared default requirements.txt file to install for python 3.6.x+ # this will default to the python interpretter running this script (probably o3de) # this should be relaced by the target interpretter python exe, like mayapy.exe # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- Check if pip is installed and log what version # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- Will use ensurepip method to ensure pip is installed #note: this doesn't work with python 3.7 which is the version o3de is on #luckily o3de comes with working pip #if this errors out with an exception and "ValueError: bad marshal data (unknown type code)" #you should try to install pip using dfoundation.install_pip() method # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- # version to download (DL) # temp dir to store in: # default file location to store it: Attempts to download the get-pip.py script # ensure what is passed in is a Path object # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- Installs pip via get-pip.py # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- # version of requirements.txt to installa Installs the DCCsi requirments.txt ## install required packages # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- Installs a pkg for DCCsi # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- Run some subprocess that captures output as ``str`` # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- cast a arg bool to a python bool # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- # ------------------------------------------------------------------------- # this will switch to run the specified dcc tools python exe and determine version # decodes byte string to string # clean # split by space, take version # splity by . to list # ------------------------------------------------------------------------- ########################################################################### # Main Code Block, runs this script as main (testing) # ------------------------------------------------------------------------- Run this file as main (external commandline) #os.environ['PYTHONINSPECT'] = 'True' # default loglevel to info unless set # override loglevel if runnign debug # configure basic logger # note: not using a common logger to reduce cyclical imports # easy overrides # then we can change the version dependant target folder for pkg install # this will verify pip is installed for the target python interpretter/env # installing the requirments.txt is enabled by default # -- DONE ---- # return # custom prompt # --- END -----------------------------------------------------------------