Commit 85950d71 by Sébastien Eustace

Add basic support for git dependencies

parent 79986e05
......@@ -5,6 +5,7 @@
### Added
- Added `remove` command.
- Added basic support for VCS (git) dependencies.
## [0.1.0] - 2018-02-28
......
......@@ -20,4 +20,8 @@ class PipInstaller:
return self._venv.run('pip', *args)
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}'
......@@ -4,7 +4,7 @@ import re
import toml
from poetry.vcs.git_config import GitConfig
from poetry.vcs.git import Git
_canonicalize_regex = re.compile(r"[-_.]+")
......@@ -28,15 +28,16 @@ class Layout(object):
self._dev_dependencies = {}
self._include = []
self._git_config = GitConfig()
self._git = Git()
git_config = self._git.config
if not author:
if (
self._git_config.get('user.name')
and self._git_config.get('user.email')
git_config.get('user.name')
and git_config.get('user.email')
):
author = '{} <{}>'.format(
self._git_config['user.name'],
self._git_config['user.email']
git_config['user.name'],
git_config['user.email']
)
else:
author = 'Your Name <you@example.com>'
......
from .dependency import Dependency
from .locker import Locker
from .package import Package
from .vcs_dependency import VCSDependency
......@@ -28,7 +28,7 @@ class Dependency:
@property
def pretty_name(self):
return '{} ({})'.format(self._name, self._pretty_constraint)
return '{} ({})'.format(self._name, self.pretty_constraint)
@property
def category(self):
......@@ -40,6 +40,9 @@ class Dependency:
def is_optional(self):
return self._optional
def is_vcs(self):
return False
def __eq__(self, other):
if not isinstance(other, Dependency):
return NotImplemented
......
......@@ -92,6 +92,11 @@ class Locker:
package.hashes = info['checksum']
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)
return packages
......@@ -166,8 +171,15 @@ class Locker:
'optional': package.optional,
'python-versions': package.python_versions,
'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
from poetry.semver.helpers import parse_stability
from .dependency import Dependency
from .vcs_dependency import VCSDependency
class Package:
......@@ -118,7 +119,14 @@ class Package:
if isinstance(constraint, dict):
if 'git' in constraint:
# 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:
version = constraint['version']
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 pathlib import Path
from tempfile import mkdtemp
from typing import Dict
from typing import List
......@@ -8,12 +15,17 @@ from poetry.mixology.contracts import SpecificationProvider
from poetry.packages import Dependency
from poetry.packages import Package
from poetry.packages import VCSDependency
from poetry.repositories.repository import Repository
from poetry.semver import less_than
from poetry.semver.constraints import Constraint
from poetry.utils.venv import Venv
from poetry.vcs.git import Git
class Provider(SpecificationProvider):
......@@ -47,6 +59,9 @@ class Provider(SpecificationProvider):
The specifications in the returned list will be considered in reverse
order, so the latest version ought to be last.
"""
if dependency.is_vcs():
return self.search_for_vcs(dependency)
packages = self._repository.find_packages(
dependency.name,
dependency.constraint
......@@ -62,8 +77,80 @@ class Provider(SpecificationProvider):
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):
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 [
r for r in package.requires
......
......@@ -2,7 +2,7 @@ import re
import subprocess
class GitConfig(object):
class GitConfig:
def __init__(self):
config_list = subprocess.check_output(
......@@ -22,3 +22,36 @@ class GitConfig(object):
def __getitem__(self, 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