Commit c10742fd by Sébastien Eustace

Merge branch 'master' into develop

parents 47a05c26 4829967a
...@@ -13,6 +13,31 @@ ...@@ -13,6 +13,31 @@
- Improved debugging of dependency resolution. - Improved debugging of dependency resolution.
## [0.8.6] - 2018-04-30
### Fixed
- Fixed config files not being created.
## [0.8.5] - 2018-04-19
### Fixed
- Fixed a bug in dependency resolution which led to installation errors.
- Fixed a bug where malformed sdists would lead to dependency resolution failing.
## [0.8.4] - 2018-04-18
### Fixed
- Fixed a bug where dependencies constraints in lock were too strict.
- Fixed unicode error in `search` command for Python 2.7.
- Fixed error with git dependencies.
>>>>>>> master
## [0.8.3] - 2018-04-16 ## [0.8.3] - 2018-04-16
### Fixed ### Fixed
...@@ -259,7 +284,10 @@ Initial release ...@@ -259,7 +284,10 @@ Initial release
[Unreleased]: https://github.com/sdispater/poetry/compare/0.8.3...master [Unreleased]: https://github.com/sdispater/poetry/compare/0.8.6...master
[0.8.6]: https://github.com/sdispater/poetry/releases/tag/0.8.6
[0.8.5]: https://github.com/sdispater/poetry/releases/tag/0.8.5
[0.8.4]: https://github.com/sdispater/poetry/releases/tag/0.8.4
[0.8.3]: https://github.com/sdispater/poetry/releases/tag/0.8.3 [0.8.3]: https://github.com/sdispater/poetry/releases/tag/0.8.3
[0.8.2]: https://github.com/sdispater/poetry/releases/tag/0.8.2 [0.8.2]: https://github.com/sdispater/poetry/releases/tag/0.8.2
[0.8.1]: https://github.com/sdispater/poetry/releases/tag/0.8.1 [0.8.1]: https://github.com/sdispater/poetry/releases/tag/0.8.1
......
...@@ -56,7 +56,7 @@ If you want to install prerelease versions, you can use the `--preview` option. ...@@ -56,7 +56,7 @@ If you want to install prerelease versions, you can use the `--preview` option.
poetry self:update --preview poetry self:update --preview
``` ```
And finally, if you want to install a spcific version you can pass it as an argument And finally, if you want to install a specific version you can pass it as an argument
to `self:update`. to `self:update`.
```bash ```bash
......
...@@ -119,6 +119,7 @@ def temporary_directory(*args, **kwargs): ...@@ -119,6 +119,7 @@ def temporary_directory(*args, **kwargs):
class Installer: class Installer:
CURRENT_PYTHON = sys.executable
METADATA_URL = 'https://pypi.org/pypi/poetry/json' METADATA_URL = 'https://pypi.org/pypi/poetry/json'
VERSION_REGEX = re.compile( VERSION_REGEX = re.compile(
'v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?' 'v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?'
...@@ -197,7 +198,7 @@ class Installer: ...@@ -197,7 +198,7 @@ class Installer:
dist = os.path.join(dir, 'dist') dist = os.path.join(dir, 'dist')
print(' - Getting dependencies') print(' - Getting dependencies')
self.call( self.call(
'python', '-m', 'pip', 'install', 'poetry=={}'.format(version), self.CURRENT_PYTHON, '-m', 'pip', 'install', 'poetry=={}'.format(version),
'--target', dist '--target', dist
) )
...@@ -243,7 +244,7 @@ class Installer: ...@@ -243,7 +244,7 @@ class Installer:
) )
self.call( self.call(
'python', '-m', 'pip', 'install', self.CURRENT_PYTHON, '-m', 'pip', 'install',
'--upgrade', '--upgrade',
'--no-deps', '--no-deps',
os.path.join(dir, 'poetry-{}-{}.whl'.format(version, tag)) os.path.join(dir, 'poetry-{}-{}.whl'.format(version, tag))
......
__version__ = '0.8.3' __version__ = '0.8.6'
...@@ -49,12 +49,12 @@ To remove a repository (repo is a short alias for repositories): ...@@ -49,12 +49,12 @@ To remove a repository (repo is a short alias for repositories):
# Create config file if it does not exist # Create config file if it does not exist
if not self._config.file.exists(): if not self._config.file.exists():
self._config.file.parent.mkdir(parents=True, exist_ok=True) self._config.file.parent.mkdir(parents=True, exist_ok=True)
with self._config.file.open() as f: with self._config.file.open('w') as f:
f.write(TEMPLATE) f.write(TEMPLATE)
if not self._auth_config.file.exists(): if not self._auth_config.file.exists():
self._auth_config.file.parent.mkdir(parents=True, exist_ok=True) self._auth_config.file.parent.mkdir(parents=True, exist_ok=True)
with self._auth_config.file.open() as f: with self._auth_config.file.open('w') as f:
f.write(AUTH_TEMPLATE) f.write(AUTH_TEMPLATE)
def handle(self): def handle(self):
......
...@@ -9,8 +9,7 @@ class InstallCommand(VenvCommand): ...@@ -9,8 +9,7 @@ class InstallCommand(VenvCommand):
{ --no-dev : Do not install dev dependencies. } { --no-dev : Do not install dev dependencies. }
{ --dry-run : Outputs the operations but will not execute anything { --dry-run : Outputs the operations but will not execute anything
(implicitly enables --verbose). } (implicitly enables --verbose). }
{ --E|extras=* : Extra sets of dependencies to install { --E|extras=* : Extra sets of dependencies to install. }
(multiple values allowed). }
""" """
help = """The <info>install</info> command reads the <comment>pyproject.toml</> file from help = """The <info>install</info> command reads the <comment>pyproject.toml</> file from
......
...@@ -322,8 +322,8 @@ class Installer: ...@@ -322,8 +322,8 @@ class Installer:
' - Updating <info>{}</> (<comment>{}</> -> <comment>{}</>)' ' - Updating <info>{}</> (<comment>{}</> -> <comment>{}</>)'
.format( .format(
target.pretty_name, target.pretty_name,
source.pretty_version, source.full_pretty_version,
target.pretty_version target.full_pretty_version
) )
) )
......
...@@ -156,6 +156,9 @@ class Provider(SpecificationProvider, UI): ...@@ -156,6 +156,9 @@ class Provider(SpecificationProvider, UI):
name = info['name'] name = info['name']
version = info['version'] version = info['version']
package = Package(name, version, version) package = Package(name, version, version)
package.source_type = dependency.vcs
package.source_url = dependency.source
package.source_reference = dependency.reference
for req_name, req_constraint in info['dependencies'].items(): for req_name, req_constraint in info['dependencies'].items():
if req_name == 'python': if req_name == 'python':
package.python_versions = req_constraint package.python_versions = req_constraint
......
...@@ -3,6 +3,7 @@ from typing import List ...@@ -3,6 +3,7 @@ from typing import List
from poetry.mixology import Resolver from poetry.mixology import Resolver
from poetry.mixology.dependency_graph import DependencyGraph from poetry.mixology.dependency_graph import DependencyGraph
from poetry.mixology.exceptions import ResolverError from poetry.mixology.exceptions import ResolverError
from poetry.packages.constraints.generic_constraint import GenericConstraint
from poetry.semver.version_parser import VersionParser from poetry.semver.version_parser import VersionParser
...@@ -43,37 +44,20 @@ class Solver: ...@@ -43,37 +44,20 @@ class Solver:
# Setting info # Setting info
for vertex in graph.vertices.values(): for vertex in graph.vertices.values():
tags = self._get_tags_for_vertex(vertex, requested) category, optional, python, platform = self._get_tags_for_vertex(
if 'main' in tags['category']: vertex, requested
vertex.payload.category = 'main' )
else:
vertex.payload.category = 'dev'
if not tags['optional']: vertex.payload.category = category
vertex.payload.optional = False vertex.payload.optional = optional
else:
vertex.payload.optional = True
# Finding the less restrictive requirements # If requirements are empty, drop them
requirements = {} requirements = {}
parser = VersionParser() if python is not None and python != '*':
for req_name, reqs in tags['requirements'].items(): requirements['python'] = python
for req in reqs:
if req_name == 'python':
if 'python' not in requirements:
requirements['python'] = req
continue
previous = parser.parse_constraints(requirements['python'])
current = parser.parse_constraints(req)
if current.matches(previous): if platform is not None and platform != '*':
requirements['python'] = req requirements['platform'] = platform
if req_name == 'platform':
if 'platform' not in requirements:
requirements['platform'] = req
continue
vertex.payload.requirements = requirements vertex.payload.requirements = requirements
...@@ -128,46 +112,109 @@ class Solver: ...@@ -128,46 +112,109 @@ class Solver:
) )
def _get_tags_for_vertex(self, vertex, requested): def _get_tags_for_vertex(self, vertex, requested):
tags = { category = 'dev'
'category': [], optional = True
'optional': True, python_version = None
'requirements': { platform = None
'python': [],
'platform': []
}
}
if not vertex.incoming_edges: if not vertex.incoming_edges:
# Original dependency # Original dependency
for req in requested: for req in requested:
if req.name == vertex.name: if vertex.payload.name == req.name:
tags['category'].append(req.category) category = req.category
if not req.is_optional(): optional = req.is_optional()
tags['optional'] = False
if req.python_versions != '*': python_version = str(req.python_constraint)
tags['requirements']['python'].append(str(req.python_constraint))
if req.platform != '*': platform = str(req.platform_constraint)
tags['requirements']['platform'].append(str(req.platform_constraint))
break break
else:
return category, optional, python_version, platform
parser = VersionParser()
python_versions = []
platforms = []
for edge in vertex.incoming_edges: for edge in vertex.incoming_edges:
python_version = None
platform = None
for req in edge.origin.payload.requires: for req in edge.origin.payload.requires:
if req.name == vertex.payload.name: if req.name == vertex.payload.name:
if req.python_versions != '*': python_version = req.python_versions
tags['requirements']['python'].append(req.python_versions) platform = req.platform
break
(top_category,
top_optional,
top_python_version,
top_platform) = self._get_tags_for_vertex(
edge.origin, requested
)
if req.platform != '*': if top_category == 'main':
tags['requirements']['platform'].append(req.platform) category = top_category
sub_tags = self._get_tags_for_vertex(edge.origin, requested) optional = optional and top_optional
tags['category'] += sub_tags['category'] # Take the most restrictive constraints
tags['optional'] = tags['optional'] and sub_tags['optional'] if top_python_version is not None:
requirements = sub_tags['requirements'] if python_version is not None:
tags['requirements']['python'] += requirements.get('python', []) previous = parser.parse_constraints(python_version)
tags['requirements']['platform'] += requirements.get('platform', []) current = parser.parse_constraints(top_python_version)
if top_python_version != '*' and previous.matches(current):
python_versions.append(top_python_version)
else:
python_versions.append(python_version)
else:
python_versions.append(top_python_version)
elif python_version is not None:
python_versions.append(python_version)
if top_platform is not None:
if platform is not None:
previous = GenericConstraint.parse(platform)
current = GenericConstraint.parse(top_platform)
if top_platform != '*' and previous.matches(current):
platforms.append(top_platform)
else:
platforms.append(platform)
else:
platforms.append(top_platform)
elif platform is not None:
platforms.append(platform)
if not python_versions:
python_version = None
else:
# Find the least restrictive constraint
python_version = python_versions[0]
previous = parser.parse_constraints(python_version)
for constraint in python_versions[1:]:
current = parser.parse_constraints(constraint)
if python_version == '*':
continue
elif constraint == '*':
python_version = constraint
elif current.matches(previous):
python_version = constraint
if not platforms:
platform = None
else:
platform = platforms[0]
previous = GenericConstraint.parse(platform)
for constraint in platforms[1:]:
current = GenericConstraint.parse(constraint)
if platform == '*':
continue
elif constraint == '*':
platform = constraint
elif current.matches(previous):
platform = constraint
return tags return category, optional, python_version, platform
...@@ -33,6 +33,7 @@ from poetry.semver.constraints import Constraint ...@@ -33,6 +33,7 @@ from poetry.semver.constraints import Constraint
from poetry.semver.constraints.base_constraint import BaseConstraint from poetry.semver.constraints.base_constraint import BaseConstraint
from poetry.semver.version_parser import VersionParser from poetry.semver.version_parser import VersionParser
from poetry.utils._compat import Path from poetry.utils._compat import Path
from poetry.utils._compat import to_str
from poetry.utils.helpers import temporary_directory from poetry.utils.helpers import temporary_directory
from poetry.version.markers import InvalidMarker from poetry.version.markers import InvalidMarker
...@@ -221,7 +222,7 @@ class PyPiRepository(Repository): ...@@ -221,7 +222,7 @@ class PyPiRepository(Repository):
for hit in hits: for hit in hits:
result = Package(hit['name'], hit['version'], hit['version']) result = Package(hit['name'], hit['version'], hit['version'])
result.description = hit['summary'] result.description = to_str(hit['summary'])
results.append(result) results.append(result)
return results return results
...@@ -358,7 +359,12 @@ class PyPiRepository(Repository): ...@@ -358,7 +359,12 @@ class PyPiRepository(Repository):
filepath = os.path.join(temp_dir, filename) filepath = os.path.join(temp_dir, filename)
self._download(url, filepath) self._download(url, filepath)
try:
meta = pkginfo.Wheel(filepath) meta = pkginfo.Wheel(filepath)
except ValueError:
# Unable to determine dependencies
# Assume none
return
if meta.requires_dist: if meta.requires_dist:
return meta.requires_dist return meta.requires_dist
...@@ -371,10 +377,15 @@ class PyPiRepository(Repository): ...@@ -371,10 +377,15 @@ class PyPiRepository(Repository):
filepath = Path(temp_dir) / filename filepath = Path(temp_dir) / filename
self._download(url, str(filepath)) self._download(url, str(filepath))
try:
meta = pkginfo.SDist(str(filepath)) meta = pkginfo.SDist(str(filepath))
if meta.requires_dist: if meta.requires_dist:
return meta.requires_dist return meta.requires_dist
except ValueError:
# Unable to determine dependencies
# We pass and go deeper
pass
# Still not dependencies found # Still not dependencies found
# So, we unpack and introspect # So, we unpack and introspect
......
...@@ -39,7 +39,7 @@ class Git: ...@@ -39,7 +39,7 @@ class Git:
return self._config return self._config
def clone(self, repository, dest): # type: (...) -> str def clone(self, repository, dest): # type: (...) -> str
return self.run('clone', repository, dest) return self.run('clone', repository, str(dest))
def checkout(self, rev, folder=None): # type: (...) -> str def checkout(self, rev, folder=None): # type: (...) -> str
args = [] args = []
......
...@@ -319,9 +319,6 @@ def test_run_with_optional_and_python_restricted_dependencies(installer, locker, ...@@ -319,9 +319,6 @@ def test_run_with_optional_and_python_restricted_dependencies(installer, locker,
installer.run() installer.run()
expected = fixture('with-optional-dependencies') expected = fixture('with-optional-dependencies')
import json
print(json.dumps(locker.written_data, indent=2, sort_keys=True))
print(json.dumps(expected, indent=2, sort_keys=True))
assert locker.written_data == expected assert locker.written_data == expected
installer = installer.installer installer = installer.installer
......
...@@ -502,3 +502,87 @@ def test_solver_does_not_return_prereleases_if_not_requested(solver, repo): ...@@ -502,3 +502,87 @@ def test_solver_does_not_return_prereleases_if_not_requested(solver, repo):
{'job': 'install', 'package': package_b}, {'job': 'install', 'package': package_b},
{'job': 'install', 'package': package_c}, {'job': 'install', 'package': package_c},
]) ])
def test_solver_sub_dependencies_with_requirements(solver, repo):
package_a = get_package('A', '1.0')
package_b = get_package('B', '1.0')
package_c = get_package('C', '1.0')
package_d = get_package('D', '1.0')
package_c.add_dependency('D', {'version': '^1.0', 'python': '<4.0'})
package_a.add_dependency('C')
package_b.add_dependency('D', '^1.0')
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
repo.add_package(package_d)
dependency_a = get_dependency('A')
dependency_b = get_dependency('B')
request = [
dependency_a,
dependency_b,
]
ops = solver.solve(request)
check_solver_result(ops, [
{'job': 'install', 'package': package_c},
{'job': 'install', 'package': package_d},
{'job': 'install', 'package': package_a},
{'job': 'install', 'package': package_b},
])
op = ops[1]
assert op.package.requirements == {}
def test_solver_sub_dependencies_with_requirements_complex(solver, repo):
package_a = get_package('A', '1.0')
package_b = get_package('B', '1.0')
package_c = get_package('C', '1.0')
package_d = get_package('D', '1.0')
package_e = get_package('E', '1.0')
package_f = get_package('F', '1.0')
package_a.add_dependency('B', '^1.0')
package_a.add_dependency('D', {'version': '^1.0', 'python': '<4.0'})
package_b.add_dependency('E', {'version': '^1.0', 'platform': 'win32'})
package_b.add_dependency('F')
package_c.add_dependency('F', '^1.0')
package_d.add_dependency('F')
repo.add_package(package_a)
repo.add_package(package_b)
repo.add_package(package_c)
repo.add_package(package_d)
repo.add_package(package_e)
repo.add_package(package_f)
dependency_a = get_dependency('A')
dependency_b = get_dependency('B')
dependency_c = get_dependency('C')
request = [
dependency_a,
dependency_b,
dependency_c,
]
ops = solver.solve(request)
check_solver_result(ops, [
{'job': 'install', 'package': package_d},
{'job': 'install', 'package': package_e},
{'job': 'install', 'package': package_f},
{'job': 'install', 'package': package_a},
{'job': 'install', 'package': package_b},
{'job': 'install', 'package': package_c},
])
op = ops[1]
assert op.package.requirements == {'platform': 'win32'}
op = ops[2]
assert op.package.requirements == {}
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