Unverified Commit 5b1f55e1 authored by Sébastien Eustace's avatar Sébastien Eustace
Browse files

Add the self:update command

parent edc9f67b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -8,6 +8,7 @@
- Added a fallback mechanism for missing dependencies.
- Added the `search` command.
- Added support for local files as dependencies.
- Added the `self:update` command.

### Changes

+7 −0
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@ from .commands import VersionCommand
from .commands.debug import DebugInfoCommand
from .commands.debug import DebugResolveCommand

from .commands.self import SelfUpdateCommand


class Application(BaseApplication):

@@ -113,4 +115,9 @@ class Application(BaseApplication):
            DebugResolveCommand(),
        ]

        # Self commands
        commands += [
            SelfUpdateCommand(),
        ]

        return commands
+1 −0
Original line number Diff line number Diff line
from .update import SelfUpdateCommand
+169 −0
Original line number Diff line number Diff line
import os
import shutil
import subprocess
import sys

from email.parser import Parser

from functools import cmp_to_key

from ..command import Command


class SelfUpdateCommand(Command):
    """
    Updates poetry to the latest version.

    self:update
        { version? : The version to update to. }
        { --preview : Install prereleases. }
    """

    def handle(self):
        from poetry.repositories.pypi_repository import PyPiRepository
        from poetry.semver.comparison import less_than

        version = self.argument('version')
        if not version:
            version = '>=' + self.poetry.VERSION

        repo = PyPiRepository(fallback=False)
        packages = repo.find_packages('poetry', version)
        if not packages:
            self.line('No release found for the specified version')
            return

        packages.sort(
            key=cmp_to_key(
                lambda x, y:
                0 if x.version == y.version
                else -1 * int(less_than(x.version, y.version) or -1)
            )
        )

        release = None
        for package in reversed(packages):
            if package.is_prerelease():
                if self.option('preview'):
                    release = package

                    break

                continue

            release = package

            break

        if release is None:
            self.line('No new release found')
            return

        if release.version == self.poetry.VERSION:
            self.line('You are using the latest version')
            return

        try:
            self.update(release)
        except subprocess.CalledProcessError as e:
            self.line('')
            self.output.block([
                '[CalledProcessError]',
                'An error has occured: {}'.format(str(e)),
                e.output
            ], style='error')

            return e.returncode

    def update(self, release):
        from poetry.utils._compat import Path
        from poetry.utils.helpers import temporary_directory

        version = release.version
        self.line('Updating to <info>{}</info>'.format(version))

        prefix = sys.prefix
        base_prefix = getattr(sys, 'base_prefix', None)
        real_prefix = getattr(sys, 'real_prefix', None)

        prefix_poetry = Path(prefix) / 'bin' / 'poetry'
        if prefix_poetry.exists():
            pip = (prefix_poetry.parent / 'pip').resolve()
        elif (
            base_prefix
            and base_prefix != prefix
            and (Path(base_prefix) / 'bin' / 'poetry').exists()
        ):
            pip = Path(base_prefix) / 'bin' / 'pip'
        elif real_prefix:
            pip = Path(real_prefix) / 'bin' / 'pip'
        else:
            raise RuntimeError('Unable to determine poetry\'s path')

        with temporary_directory(prefix='poetry-update-') as temp_dir:
            temp_dir = Path(temp_dir)
            dist = temp_dir / 'dist'
            self.line('  - Getting dependencies')
            self.process(
                str(pip), 'install', '-U',
                'poetry=={}'.format(release.version),
                '--target', str(dist)
            )

            self.line('  - Vendorizing dependencies')

            poetry_dir = dist / 'poetry'
            vendor_dir = poetry_dir / '_vendor'

            # Everything, except poetry itself, should
            # be put in the _vendor directory
            for file in dist.glob('*'):
                if file.name.startswith('poetry'):
                    continue

                dest = vendor_dir / file.name
                if file.is_dir():
                    shutil.copytree(str(file), str(dest))
                    shutil.rmtree(str(file))
                else:
                    shutil.copy(str(file), str(dest))
                    os.unlink(str(file))

            wheel_data = dist / 'poetry-{}.dist-info'.format(version) / 'WHEEL'
            with wheel_data.open() as f:
                wheel_data = Parser().parsestr(f.read())

            tag = wheel_data['Tag']

            # Repack everything and install
            self.line('  - Updating <info>poetry</info>')

            shutil.make_archive(
                str(temp_dir / 'poetry-{}-{}'.format(version, tag)),
                format='zip',
                root_dir=str(dist)
            )

            os.rename(
                str(temp_dir / 'poetry-{}-{}.zip'.format(version, tag)),
                str(temp_dir / 'poetry-{}-{}.whl'.format(version, tag)),
            )

            self.process(
                str(pip),
                'install',
                '--upgrade',
                '--no-deps',
                str(temp_dir / 'poetry-{}-{}.whl'.format(version, tag))
            )

            self.line('')
            self.line(
                '<info>poetry</> (<comment>{}</>) '
                'successfully installed!'.format(
                    version
                )
            )

    def process(self, *args):
        return subprocess.check_output(list(args), stderr=subprocess.STDOUT)