Commit 85950d71 by Sébastien Eustace

Add basic support for git dependencies

parent 79986e05
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
### Added ### Added
- Added `remove` command. - Added `remove` command.
- Added basic support for VCS (git) dependencies.
## [0.1.0] - 2018-02-28 ## [0.1.0] - 2018-02-28
......
...@@ -20,4 +20,8 @@ class PipInstaller: ...@@ -20,4 +20,8 @@ class PipInstaller:
return self._venv.run('pip', *args) return self._venv.run('pip', *args)
def requirement(self, package) -> str: def requirement(self, package) -> str:
if package.source_type == 'git':
return f'git+{package.source_url}@{package.source_reference}' \
f'#egg={package.name}'
return f'{package.name}=={package.version}' return f'{package.name}=={package.version}'
...@@ -4,7 +4,7 @@ import re ...@@ -4,7 +4,7 @@ import re
import toml import toml
from poetry.vcs.git_config import GitConfig from poetry.vcs.git import Git
_canonicalize_regex = re.compile(r"[-_.]+") _canonicalize_regex = re.compile(r"[-_.]+")
...@@ -28,15 +28,16 @@ class Layout(object): ...@@ -28,15 +28,16 @@ class Layout(object):
self._dev_dependencies = {} self._dev_dependencies = {}
self._include = [] self._include = []
self._git_config = GitConfig() self._git = Git()
git_config = self._git.config
if not author: if not author:
if ( if (
self._git_config.get('user.name') git_config.get('user.name')
and self._git_config.get('user.email') and git_config.get('user.email')
): ):
author = '{} <{}>'.format( author = '{} <{}>'.format(
self._git_config['user.name'], git_config['user.name'],
self._git_config['user.email'] git_config['user.email']
) )
else: else:
author = 'Your Name <you@example.com>' author = 'Your Name <you@example.com>'
......
from .dependency import Dependency from .dependency import Dependency
from .locker import Locker from .locker import Locker
from .package import Package from .package import Package
from .vcs_dependency import VCSDependency
...@@ -28,7 +28,7 @@ class Dependency: ...@@ -28,7 +28,7 @@ class Dependency:
@property @property
def pretty_name(self): def pretty_name(self):
return '{} ({})'.format(self._name, self._pretty_constraint) return '{} ({})'.format(self._name, self.pretty_constraint)
@property @property
def category(self): def category(self):
...@@ -40,6 +40,9 @@ class Dependency: ...@@ -40,6 +40,9 @@ class Dependency:
def is_optional(self): def is_optional(self):
return self._optional return self._optional
def is_vcs(self):
return False
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, Dependency): if not isinstance(other, Dependency):
return NotImplemented return NotImplemented
......
...@@ -92,6 +92,11 @@ class Locker: ...@@ -92,6 +92,11 @@ class Locker:
package.hashes = info['checksum'] package.hashes = info['checksum']
package.python_versions = info['python-versions'] package.python_versions = info['python-versions']
if 'source' in info:
package.source_type = info['source']['type']
package.source_url = info['source']['url']
package.source_reference = info['source']['reference']
packages.add_package(package) packages.add_package(package)
return packages return packages
...@@ -166,8 +171,15 @@ class Locker: ...@@ -166,8 +171,15 @@ class Locker:
'optional': package.optional, 'optional': package.optional,
'python-versions': package.python_versions, 'python-versions': package.python_versions,
'platform': package.platform, 'platform': package.platform,
'checksum': package.hashes 'checksum': package.hashes,
} }
if package.source_type:
data['source'] = {
'type': package.source_type,
'url': package.source_url,
'reference': package.source_reference
}
return data return data
from poetry.semver.helpers import parse_stability from poetry.semver.helpers import parse_stability
from .dependency import Dependency from .dependency import Dependency
from .vcs_dependency import VCSDependency
class Package: class Package:
...@@ -118,7 +119,14 @@ class Package: ...@@ -118,7 +119,14 @@ class Package:
if isinstance(constraint, dict): if isinstance(constraint, dict):
if 'git' in constraint: if 'git' in constraint:
# VCS dependency # VCS dependency
pass dependency = VCSDependency(
name,
'git', constraint['git'],
branch=constraint.get('branch', None),
tag=constraint.get('tag', None),
rev=constraint.get('rev', None),
optional=constraint.get('optional', None),
)
else: else:
version = constraint['version'] version = constraint['version']
optional = constraint.get('optional', False) optional = constraint.get('optional', False)
......
from .dependency import Dependency
class VCSDependency(Dependency):
"""
Represents a VCS dependency
"""
def __init__(self, name, vcs, source,
branch=None, tag=None, rev=None,
optional=False):
self._vcs = vcs
self._source = source
if not any([branch, tag, rev]):
# If nothing has been specified, we assume master
branch = 'master'
self._branch = branch
self._tag = tag
self._rev = rev
super(VCSDependency, self).__init__(name, '*', optional=optional)
@property
def vcs(self) -> str:
return self._vcs
@property
def source(self):
return self._source
@property
def branch(self):
return self._branch
@property
def tag(self):
return self._tag
@property
def rev(self):
return self._rev
@property
def reference(self) -> str:
return self._branch or self._tag or self._rev
@property
def pretty_constraint(self) -> str:
if self._branch:
what = 'branch'
version = self._branch
elif self._tag:
what = 'tag'
version = self._tag
else:
what = 'rev'
version = self._rev
return f'{what} {version}'
def is_vcs(self) -> bool:
return True
import os
import shutil
import toml
from functools import cmp_to_key from functools import cmp_to_key
from pathlib import Path
from tempfile import mkdtemp
from typing import Dict from typing import Dict
from typing import List from typing import List
...@@ -8,12 +15,17 @@ from poetry.mixology.contracts import SpecificationProvider ...@@ -8,12 +15,17 @@ from poetry.mixology.contracts import SpecificationProvider
from poetry.packages import Dependency from poetry.packages import Dependency
from poetry.packages import Package from poetry.packages import Package
from poetry.packages import VCSDependency
from poetry.repositories.repository import Repository from poetry.repositories.repository import Repository
from poetry.semver import less_than from poetry.semver import less_than
from poetry.semver.constraints import Constraint from poetry.semver.constraints import Constraint
from poetry.utils.venv import Venv
from poetry.vcs.git import Git
class Provider(SpecificationProvider): class Provider(SpecificationProvider):
...@@ -47,6 +59,9 @@ class Provider(SpecificationProvider): ...@@ -47,6 +59,9 @@ class Provider(SpecificationProvider):
The specifications in the returned list will be considered in reverse The specifications in the returned list will be considered in reverse
order, so the latest version ought to be last. order, so the latest version ought to be last.
""" """
if dependency.is_vcs():
return self.search_for_vcs(dependency)
packages = self._repository.find_packages( packages = self._repository.find_packages(
dependency.name, dependency.name,
dependency.constraint dependency.constraint
...@@ -62,8 +77,80 @@ class Provider(SpecificationProvider): ...@@ -62,8 +77,80 @@ class Provider(SpecificationProvider):
return packages return packages
def search_for_vcs(self, dependency: VCSDependency) -> List[Package]:
"""
Search for the specifications that match the given VCS dependency.
Basically, we clone the repository in a temporary directory
and get the information we need by checking out the specified reference.
"""
if dependency.vcs != 'git':
raise ValueError(f'Unsupported VCS dependency {dependency.vcs}')
tmp_dir = Path(mkdtemp(prefix=f'pypoetry-git-{dependency.name}'))
try:
git = Git()
git.clone(dependency.source, tmp_dir)
git.checkout(dependency.reference, tmp_dir)
revision = git.rev_parse(
dependency.reference, tmp_dir
).strip()
if dependency.tag or dependency.rev:
revision = dependency.reference
if (tmp_dir / 'poetry.toml').exists():
# If a poetry.toml file exists
# We use it to get the information we need
with (tmp_dir / 'poetry.toml').open() as fd:
info = toml.loads(fd.read())
name = info['package']['name']
version = info['package']['version']
package = Package(name, version, version)
for req_name, req_constraint in info['dependencies'].items():
package.add_dependency(req_name, req_constraint)
else:
# We need to use setup.py here
# to figure the information we need
# We need to place ourselves in the proper
# folder for it to work
current_dir = os.getcwd()
os.chdir(tmp_dir.as_posix())
try:
venv = Venv.create()
output = venv.run(
'python', 'setup.py',
'--name', '--version'
)
output = output.split('\n')
name = output[-3]
version = output[-2]
package = Package(name, version, version)
# Figure out a way to get requirements
except Exception:
raise
finally:
os.chdir(current_dir)
package.source_type = 'git'
package.source_url = dependency.source
package.source_reference = revision
except Exception:
raise
finally:
shutil.rmtree(tmp_dir.as_posix())
return [package]
def dependencies_for(self, package: Package): def dependencies_for(self, package: Package):
package = self._repository.package(package.name, package.version) if package.source_type == 'git':
# Information should already be set
pass
else:
package = self._repository.package(package.name, package.version)
return [ return [
r for r in package.requires r for r in package.requires
......
...@@ -2,7 +2,7 @@ import re ...@@ -2,7 +2,7 @@ import re
import subprocess import subprocess
class GitConfig(object): class GitConfig:
def __init__(self): def __init__(self):
config_list = subprocess.check_output( config_list = subprocess.check_output(
...@@ -22,3 +22,36 @@ class GitConfig(object): ...@@ -22,3 +22,36 @@ class GitConfig(object):
def __getitem__(self, item): def __getitem__(self, item):
return self._config[item] return self._config[item]
class Git:
def __init__(self):
self._config = GitConfig()
@property
def config(self) -> GitConfig:
return self._config
def clone(self, repository, dest) -> str:
return self.run('clone', repository, dest)
def checkout(self, rev, folder) -> str:
return self.run(
'--git-dir', (folder / '.git').as_posix(),
'--work-tree', folder.as_posix(),
'checkout', rev
)
def rev_parse(self, rev, folder) -> str:
return self.run(
'--git-dir', (folder / '.git').as_posix(),
'--work-tree', folder.as_posix(),
'rev-parse', rev
)
def run(self, *args) -> str:
return subprocess.check_output(
['git'] + list(args),
stderr=subprocess.STDOUT
).decode()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment