<reponame>ColinKennedy/rezzurect
#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''A set of general adapter classes which can be used to build Rez packages.'''

# IMPORT STANDARD LIBRARIES
import subprocess
import functools
import logging
import stat
import os

# IMPORT LOCAL LIBRARIES
from .. import base_builder
from ... import chooser
from . import helper


_DEFAULT_VALUE = object()
_LOGGER = logging.getLogger('rezzurect.nuke_installation_builder')


class BaseNukeAdapter(base_builder.BaseAdapter):

    '''An base-class which is meant share code across subclasses.

    This class is not meant to be used directly.

    Attributes:
        _install_file_name_template (str):
            The name of the actual file which can be used to install Nuke.
            The exact name should be whatever the third-party vendor prefers.
            Make sure to include any version information in the name, such as
            "Nuke{major}.{minor}v{patch}-win-x86-release-64.exe" or
            "Nuke{major}.{minor}v{patch}-linux-x86-release-64-installer".

    '''

    _install_archive_name_template = ''
    _install_file_name_template = ''

    @classmethod
    def get_archive_path_from_version(cls, source, version):
        '''str: Get the recommended folder for archive (installer) files to be.'''
        try:
            major, minor, patch = helper.get_version_parts(version)
        except TypeError:
            major, minor, patch = version

        file_name = cls._install_archive_name_template.format(
            major=major, minor=minor, patch=patch,
        )

        return cls.get_archive_path(source, file_name)

    @classmethod
    def get_install_file(cls, root, version):
        '''Get the absolute path to where the expected Nuke install file is.

        Args:
            root (str):
                The absolute path to the package folder where the Nuke executable
                would be found.
            version (str):
                The full, unparsed version information to get an executable of.
                Example: "11.2v3".

        Returns:
            str: The absolute path to the executable file of the given `version`.

        '''
        major, minor, patch = helper.get_version_parts(version)

        return cls.get_archive_path(
            root,
            cls._install_file_name_template.format(major=major, minor=minor, patch=patch),
        )

    def install_from_local(self, source, install):
        '''Search for an extracted Nuke file and install it, if it exists.

        Args:
            source (str):
                The absolute path to the package folder where the Nuke executable
                would be found.
            install (str):
                The absolute directory where `source` will be installed into.

        Raises:
            EnvironmentError: If the executable file could not be found.

        Returns:
            str: The absolute path to the executable file which is used for installation.

        '''
        executable = self.get_install_file(source, self.version)

        if not os.path.isfile(executable):
            raise EnvironmentError(
                'install_file "{executable}" does not exist. '
                'Check its spelling and try again.'.format(executable=executable))

        return executable


class LinuxAdapter(BaseNukeAdapter):

    '''An adapter for installing Nuke onto a Linux machine.'''

    name = 'nuke'
    _install_archive_name_template = 'Nuke{major}.{minor}v{patch}-linux-x86-release-64.tgz'
    _install_file_name_template = 'Nuke{major}.{minor}v{patch}-linux-x86-release-64-installer'

    def get_preinstalled_executables(self):
        '''Get a list of possible pre-installed executable Nuke files.

        Raises:
            RuntimeError:
                If we can't get version information from the stored version then
                this function will fail. Normally though, assuming this adapter
                was built correctly, this shouldn't occur.

        Returns:
            str: The absolute path to a Nuke executable.

        '''
        return helper.get_preinstalled_linux_executables(self.version)

    def install_from_local(self, source, install):
        '''Unzip the Nuke file to the given install folder.

        Args:
            source (str):
                The absolute path to the package folder where the Nuke executable
                would be found.
            install (str):
                The absolute directory where `source` will be installed into.

        Raises:
            EnvironmentError: If the Zip file failed to extract Nuke into `install`.

        '''
        try:
            zip_file_path = super(LinuxAdapter, self).install_from_local(source, install)
        except EnvironmentError:
            self._extract_tar(source, self.version)
            zip_file_path = super(LinuxAdapter, self).install_from_local(source, install)

        _LOGGER.debug('Unzipping "%s".', zip_file_path)

        self._extract_zip(zip_file_path, install)

        major, minor, _ = helper.get_version_parts(self.version)
        executable = 'Nuke{major}.{minor}'.format(major=major, minor=minor)
        executable = os.path.join(install, executable)

        if not os.path.isfile(executable):
            raise EnvironmentError('Zip failed to extract to folder "{install}".'
                                   ''.format(install=install))

        # Reference: https://stackoverflow.com/questions/12791997
        executable_stats = os.stat(executable)
        os.chmod(executable, executable_stats.st_mode | stat.S_IEXEC)


class WindowsAdapter(BaseNukeAdapter):

    '''An adapter for installing Nuke onto a Windows machine.'''

    name = 'nuke'
    _install_archive_name_template = 'Nuke{major}.{minor}v{patch}-win-x86-release-64.zip'
    _install_file_name_template = 'Nuke{major}.{minor}v{patch}-win-x86-release-64.exe'

    # https://learn.foundry.com/nuke/content/getting_started/installation/installing_nuke_win.html
    @staticmethod
    def _get_base_command(executable, root):
        '''Make the command used to install Nuke, on Windows.

        Args:
            executable (str): The absolute path to the ".exe" installer file to run.
            root (str): The absolute directory to where the ".exe" will send files to.

        '''
        # Note: The ".exe" file normally gives you a few window prompts to have
        #       to click through.  We add "/silent" to skip these prompts and
        #       go straight to installation. Use "/verysilent" to hide the
        #       install progress bar.
        #
        return '{executable} /dir="{root}" /silent'.format(
            executable=executable, root=root)

    @classmethod
    def _extract_zip_from_version(cls, source, version):
        '''Find the ZIP file for Nuke, based on the given version, and extract it.

        Args:
            source (str):
                The root folder which contains the ZIP file.
            version (str):
                Some Nuke version to get the ZIP file of.

        Raises:
            EnvironmentError: If the found ZIP file for `version` does not exist.

        '''
        path = cls.get_archive_path_from_version(source, version)

        if not os.path.isfile(path):
            raise EnvironmentError('Zip file "{path}" does not exist.'
                                   ''.format(path=path))

        _LOGGER.debug('Extracting zip file "%s" using version, "%s"', path, str(version))

        cls._extract_zip(path, os.path.dirname(path))

    def get_preinstalled_executables(self):
        '''Get a list of possible pre-installed executable Nuke files.

        Raises:
            RuntimeError:
                If we can't get version information from the stored version then
                this function will fail. Normally though, assuming this adapter
                was built correctly, this shouldn't occur.

        Returns:
            str: The absolute path to a Nuke executable.

        '''
        return helper.get_preinstalled_windows_executables(self.version)

    def install_from_local(self, source, install):
        '''Search for a local Nuke install-executable and run it, if it exists.

        Args:
            source (str):
                The absolute path to the package folder where the Nuke executable
                would be found.
            install (str):
                The absolute directory where `source` will be installed into.

        Raises:
            RuntimeError: If the installation failed for some reason.

        '''
        try:
            executable = super(WindowsAdapter, self).install_from_local(source, install)
        except EnvironmentError:
            self._extract_zip_from_version(source, self.version)
            executable = super(WindowsAdapter, self).install_from_local(source, install)

        command = self._get_base_command(executable, install)

        _, stderr = subprocess.Popen(
            command, stderr=subprocess.PIPE, shell=True).communicate()

        if stderr:
            raise RuntimeError('The local install failed with this message "{stderr}".'
                               ''.format(stderr=stderr))


def register(source_path, install_path, system, architecture):
    '''Add installation options to all of the Nuke adapter classes.

    Each of the installation options take only one arg, which is the adapter
    which is used to handle the request. If the request is successful then the
    function should just complete. If it fails for whatever reason then
    have the function raise any exception.

    Args:
        source_path (str):
            The absolute path to where the Rez package is located, on-disk.
        install_path (str):
            The absolute path to where the package will be installed into.
        system (str):
            The name of the OS platform. Example: "Linux", "Windows", etc.
        architecture (str):
            The bits of the `system`. Example: "x86_64", "AMD64", etc.

    '''
    adapters = (
        ('Linux', LinuxAdapter),
        ('Windows', WindowsAdapter),
    )

    for system_, adapter in adapters:
        adapter.strategies = []

        add_nuke_from_internet_build = functools.partial(
            base_builder.add_from_internet_build,
            'nuke', system, architecture, source_path, install_path)
        add_nuke_local_filesystem_build = functools.partial(
            base_builder.add_local_filesystem_build, source_path, install_path)

        adapter.strategies.append(('local', add_nuke_local_filesystem_build))
        adapter.strategies.append(('internet', add_nuke_from_internet_build))
        adapter.strategies.append(('link', base_builder.add_link_build))

        chooser.register_build_adapter(adapter, 'nuke_installation', system_)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

'''A set of general adapter classes which can be used to build Rez packages.'''

# IMPORT STANDARD LIBRARIES
import subprocess
import functools
import logging
import stat
import os

# IMPORT LOCAL LIBRARIES
from .. import base_builder
from ... import chooser
from . import helper


_DEFAULT_VALUE = object()
_LOGGER = logging.getLogger('rezzurect.nuke_installation_builder')


class BaseNukeAdapter(base_builder.BaseAdapter):

    '''An base-class which is meant share code across subclasses.

    This class is not meant to be used directly.

    Attributes:
        _install_file_name_template (str):
            The name of the actual file which can be used to install Nuke.
            The exact name should be whatever the third-party vendor prefers.
            Make sure to include any version information in the name, such as
            "Nuke{major}.{minor}v{patch}-win-x86-release-64.exe" or
            "Nuke{major}.{minor}v{patch}-linux-x86-release-64-installer".

    '''

    _install_archive_name_template = ''
    _install_file_name_template = ''

    @classmethod
    def get_archive_path_from_version(cls, source, version):
        '''str: Get the recommended folder for archive (installer) files to be.'''
        try:
            major, minor, patch = helper.get_version_parts(version)
        except TypeError:
            major, minor, patch = version

        file_name = cls._install_archive_name_template.format(
            major=major, minor=minor, patch=patch,
        )

        return cls.get_archive_path(source, file_name)

    @classmethod
    def get_install_file(cls, root, version):
        '''Get the absolute path to where the expected Nuke install file is.

        Args:
            root (str):
                The absolute path to the package folder where the Nuke executable
                would be found.
            version (str):
                The full, unparsed version information to get an executable of.
                Example: "11.2v3".

        Returns:
            str: The absolute path to the executable file of the given `version`.

        '''
        major, minor, patch = helper.get_version_parts(version)

        return cls.get_archive_path(
            root,
            cls._install_file_name_template.format(major=major, minor=minor, patch=patch),
        )

    def install_from_local(self, source, install):
        '''Search for an extracted Nuke file and install it, if it exists.

        Args:
            source (str):
                The absolute path to the package folder where the Nuke executable
                would be found.
            install (str):
                The absolute directory where `source` will be installed into.

        Raises:
            EnvironmentError: If the executable file could not be found.

        Returns:
            str: The absolute path to the executable file which is used for installation.

        '''
        executable = self.get_install_file(source, self.version)

        if not os.path.isfile(executable):
            raise EnvironmentError(
                'install_file "{executable}" does not exist. '
                'Check its spelling and try again.'.format(executable=executable))

        return executable


class LinuxAdapter(BaseNukeAdapter):

    '''An adapter for installing Nuke onto a Linux machine.'''

    name = 'nuke'
    _install_archive_name_template = 'Nuke{major}.{minor}v{patch}-linux-x86-release-64.tgz'
    _install_file_name_template = 'Nuke{major}.{minor}v{patch}-linux-x86-release-64-installer'

    def get_preinstalled_executables(self):
        '''Get a list of possible pre-installed executable Nuke files.

        Raises:
            RuntimeError:
                If we can't get version information from the stored version then
                this function will fail. Normally though, assuming this adapter
                was built correctly, this shouldn't occur.

        Returns:
            str: The absolute path to a Nuke executable.

        '''
        return helper.get_preinstalled_linux_executables(self.version)

    def install_from_local(self, source, install):
        '''Unzip the Nuke file to the given install folder.

        Args:
            source (str):
                The absolute path to the package folder where the Nuke executable
                would be found.
            install (str):
                The absolute directory where `source` will be installed into.

        Raises:
            EnvironmentError: If the Zip file failed to extract Nuke into `install`.

        '''
        try:
            zip_file_path = super(LinuxAdapter, self).install_from_local(source, install)
        except EnvironmentError:
            self._extract_tar(source, self.version)
            zip_file_path = super(LinuxAdapter, self).install_from_local(source, install)

        _LOGGER.debug('Unzipping "%s".', zip_file_path)

        self._extract_zip(zip_file_path, install)

        major, minor, _ = helper.get_version_parts(self.version)
        executable = 'Nuke{major}.{minor}'.format(major=major, minor=minor)
        executable = os.path.join(install, executable)

        if not os.path.isfile(executable):
            raise EnvironmentError('Zip failed to extract to folder "{install}".'
                                   ''.format(install=install))

        # Reference: https://stackoverflow.com/questions/12791997
        executable_stats = os.stat(executable)
        os.chmod(executable, executable_stats.st_mode | stat.S_IEXEC)


class WindowsAdapter(BaseNukeAdapter):

    '''An adapter for installing Nuke onto a Windows machine.'''

    name = 'nuke'
    _install_archive_name_template = 'Nuke{major}.{minor}v{patch}-win-x86-release-64.zip'
    _install_file_name_template = 'Nuke{major}.{minor}v{patch}-win-x86-release-64.exe'

    # https://learn.foundry.com/nuke/content/getting_started/installation/installing_nuke_win.html
    @staticmethod
    def _get_base_command(executable, root):
        '''Make the command used to install Nuke, on Windows.

        Args:
            executable (str): The absolute path to the ".exe" installer file to run.
            root (str): The absolute directory to where the ".exe" will send files to.

        '''
        # Note: The ".exe" file normally gives you a few window prompts to have
        #       to click through.  We add "/silent" to skip these prompts and
        #       go straight to installation. Use "/verysilent" to hide the
        #       install progress bar.
        #
        return '{executable} /dir="{root}" /silent'.format(
            executable=executable, root=root)

    @classmethod
    def _extract_zip_from_version(cls, source, version):
        '''Find the ZIP file for Nuke, based on the given version, and extract it.

        Args:
            source (str):
                The root folder which contains the ZIP file.
            version (str):
                Some Nuke version to get the ZIP file of.

        Raises:
            EnvironmentError: If the found ZIP file for `version` does not exist.

        '''
        path = cls.get_archive_path_from_version(source, version)

        if not os.path.isfile(path):
            raise EnvironmentError('Zip file "{path}" does not exist.'
                                   ''.format(path=path))

        _LOGGER.debug('Extracting zip file "%s" using version, "%s"', path, str(version))

        cls._extract_zip(path, os.path.dirname(path))

    def get_preinstalled_executables(self):
        '''Get a list of possible pre-installed executable Nuke files.

        Raises:
            RuntimeError:
                If we can't get version information from the stored version then
                this function will fail. Normally though, assuming this adapter
                was built correctly, this shouldn't occur.

        Returns:
            str: The absolute path to a Nuke executable.

        '''
        return helper.get_preinstalled_windows_executables(self.version)

    def install_from_local(self, source, install):
        '''Search for a local Nuke install-executable and run it, if it exists.

        Args:
            source (str):
                The absolute path to the package folder where the Nuke executable
                would be found.
            install (str):
                The absolute directory where `source` will be installed into.

        Raises:
            RuntimeError: If the installation failed for some reason.

        '''
        try:
            executable = super(WindowsAdapter, self).install_from_local(source, install)
        except EnvironmentError:
            self._extract_zip_from_version(source, self.version)
            executable = super(WindowsAdapter, self).install_from_local(source, install)

        command = self._get_base_command(executable, install)

        _, stderr = subprocess.Popen(
            command, stderr=subprocess.PIPE, shell=True).communicate()

        if stderr:
            raise RuntimeError('The local install failed with this message "{stderr}".'
                               ''.format(stderr=stderr))


def register(source_path, install_path, system, architecture):
    '''Add installation options to all of the Nuke adapter classes.

    Each of the installation options take only one arg, which is the adapter
    which is used to handle the request. If the request is successful then the
    function should just complete. If it fails for whatever reason then
    have the function raise any exception.

    Args:
        source_path (str):
            The absolute path to where the Rez package is located, on-disk.
        install_path (str):
            The absolute path to where the package will be installed into.
        system (str):
            The name of the OS platform. Example: "Linux", "Windows", etc.
        architecture (str):
            The bits of the `system`. Example: "x86_64", "AMD64", etc.

    '''
    adapters = (
        ('Linux', LinuxAdapter),
        ('Windows', WindowsAdapter),
    )

    for system_, adapter in adapters:
        adapter.strategies = []

        add_nuke_from_internet_build = functools.partial(
            base_builder.add_from_internet_build,
            'nuke', system, architecture, source_path, install_path)
        add_nuke_local_filesystem_build = functools.partial(
            base_builder.add_local_filesystem_build, source_path, install_path)

        adapter.strategies.append(('local', add_nuke_local_filesystem_build))
        adapter.strategies.append(('internet', add_nuke_from_internet_build))
        adapter.strategies.append(('link', base_builder.add_link_build))

        chooser.register_build_adapter(adapter, 'nuke_installation', system_)
#!/usr/bin/env python # -*- coding: utf-8 -*- A set of general adapter classes which can be used to build Rez packages. # IMPORT STANDARD LIBRARIES # IMPORT LOCAL LIBRARIES An base-class which is meant share code across subclasses.      This class is not meant to be used directly.      Attributes:         _install_file_name_template (str):             The name of the actual file which can be used to install Nuke.             The exact name should be whatever the third-party vendor prefers.             Make sure to include any version information in the name, such as             "Nuke{major}.{minor}v{patch}-win-x86-release-64.exe" or             "Nuke{major}.{minor}v{patch}-linux-x86-release-64-installer". str: Get the recommended folder for archive (installer) files to be. Get the absolute path to where the expected Nuke install file is.          Args:             root (str):                 The absolute path to the package folder where the Nuke executable                 would be found.             version (str):                 The full, unparsed version information to get an executable of.                 Example: "11.2v3".          Returns:             str: The absolute path to the executable file of the given `version`. Search for an extracted Nuke file and install it, if it exists.          Args:             source (str):                 The absolute path to the package folder where the Nuke executable                 would be found.             install (str):                 The absolute directory where `source` will be installed into.          Raises:             EnvironmentError: If the executable file could not be found.          Returns:             str: The absolute path to the executable file which is used for installation. An adapter for installing Nuke onto a Linux machine. Get a list of possible pre-installed executable Nuke files.          Raises:             RuntimeError:                 If we can't get version information from the stored version then                 this function will fail. Normally though, assuming this adapter                 was built correctly, this shouldn't occur.          Returns:             str: The absolute path to a Nuke executable. Unzip the Nuke file to the given install folder.          Args:             source (str):                 The absolute path to the package folder where the Nuke executable                 would be found.             install (str):                 The absolute directory where `source` will be installed into.          Raises:             EnvironmentError: If the Zip file failed to extract Nuke into `install`. # Reference: https://stackoverflow.com/questions/12791997 An adapter for installing Nuke onto a Windows machine. # https://learn.foundry.com/nuke/content/getting_started/installation/installing_nuke_win.html Make the command used to install Nuke, on Windows.          Args:             executable (str): The absolute path to the ".exe" installer file to run.             root (str): The absolute directory to where the ".exe" will send files to. # Note: The ".exe" file normally gives you a few window prompts to have #       to click through.  We add "/silent" to skip these prompts and #       go straight to installation. Use "/verysilent" to hide the #       install progress bar. # Find the ZIP file for Nuke, based on the given version, and extract it.          Args:             source (str):                 The root folder which contains the ZIP file.             version (str):                 Some Nuke version to get the ZIP file of.          Raises:             EnvironmentError: If the found ZIP file for `version` does not exist. Get a list of possible pre-installed executable Nuke files.          Raises:             RuntimeError:                 If we can't get version information from the stored version then                 this function will fail. Normally though, assuming this adapter                 was built correctly, this shouldn't occur.          Returns:             str: The absolute path to a Nuke executable. Search for a local Nuke install-executable and run it, if it exists.          Args:             source (str):                 The absolute path to the package folder where the Nuke executable                 would be found.             install (str):                 The absolute directory where `source` will be installed into.          Raises:             RuntimeError: If the installation failed for some reason. Add installation options to all of the Nuke adapter classes.      Each of the installation options take only one arg, which is the adapter     which is used to handle the request. If the request is successful then the     function should just complete. If it fails for whatever reason then     have the function raise any exception.      Args:         source_path (str):             The absolute path to where the Rez package is located, on-disk.         install_path (str):             The absolute path to where the package will be installed into.         system (str):             The name of the OS platform. Example: "Linux", "Windows", etc.         architecture (str):             The bits of the `system`. Example: "x86_64", "AMD64", etc.