mirror of
https://github.com/enpaul/tox-poetry-installer.git
synced 2025-01-15 08:43:29 +00:00
Fix non-deterministic dependency order resolution
Unordered sets strike again. By casting a list of packages to a set to ensure uniqueness the installation of the packages becomes non-deterministic. This is not great, but it trivially breaks installing packages that require their dependencies for installation. Fixes #41
This commit is contained in:
parent
52c08e9dc5
commit
ea8bc3887e
@ -97,23 +97,18 @@ def tox_testenv_install_deps(venv: ToxVirtualEnv, action: ToxAction) -> Optional
|
|||||||
}
|
}
|
||||||
|
|
||||||
if venv.envconfig.install_dev_deps:
|
if venv.envconfig.install_dev_deps:
|
||||||
dev_deps: List[PoetryPackage] = [
|
dev_deps = utilities.find_dev_dependencies(poetry, package_map)
|
||||||
dep
|
|
||||||
for dep in package_map.values()
|
|
||||||
if dep not in poetry.locker.locked_repository(False).packages
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
dev_deps = []
|
|
||||||
|
|
||||||
tox.reporter.verbosity1(
|
tox.reporter.verbosity1(
|
||||||
f"{constants.REPORTER_PREFIX} Identified {len(dev_deps)} development dependencies to install to env"
|
f"{constants.REPORTER_PREFIX} Identified {len(dev_deps)} development dependencies to install to env"
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
env_deps: List[PoetryPackage] = []
|
dev_deps = []
|
||||||
for dep in venv.envconfig.locked_deps:
|
tox.reporter.verbosity1(
|
||||||
env_deps += utilities.find_transients(
|
f"{constants.REPORTER_PREFIX} Env does not install development dependencies, skipping"
|
||||||
package_map, dep.lower(), allow_missing=[poetry.package.name]
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
env_deps = utilities.find_env_dependencies(venv, poetry, package_map)
|
||||||
|
|
||||||
tox.reporter.verbosity1(
|
tox.reporter.verbosity1(
|
||||||
f"{constants.REPORTER_PREFIX} Identified {len(env_deps)} environment dependencies to install to env"
|
f"{constants.REPORTER_PREFIX} Identified {len(env_deps)} environment dependencies to install to env"
|
||||||
)
|
)
|
||||||
@ -139,7 +134,7 @@ def tox_testenv_install_deps(venv: ToxVirtualEnv, action: ToxAction) -> Optional
|
|||||||
tox.reporter.error(f"{constants.REPORTER_PREFIX} Internal plugin error: {err}")
|
tox.reporter.error(f"{constants.REPORTER_PREFIX} Internal plugin error: {err}")
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
dependencies = list(set(dev_deps + env_deps + project_deps))
|
dependencies = dev_deps + env_deps + project_deps
|
||||||
action.setactivity(
|
action.setactivity(
|
||||||
__about__.__title__,
|
__about__.__title__,
|
||||||
f"Installing {len(dependencies)} dependencies from Poetry lock file",
|
f"Installing {len(dependencies)} dependencies from Poetry lock file",
|
||||||
|
@ -48,9 +48,9 @@ def install_to_venv(
|
|||||||
installer.install(dependency)
|
installer.install(dependency)
|
||||||
|
|
||||||
|
|
||||||
def find_transients(
|
def identify_transients(
|
||||||
packages: PackageMap, dependency_name: str, allow_missing: Sequence[str] = ()
|
packages: PackageMap, dependency_name: str, allow_missing: Sequence[str] = ()
|
||||||
) -> Set[PoetryPackage]:
|
) -> List[PoetryPackage]:
|
||||||
"""Using a poetry object identify all dependencies of a specific dependency
|
"""Using a poetry object identify all dependencies of a specific dependency
|
||||||
|
|
||||||
:param packages: All packages from the lockfile to use for identifying dependency relationships.
|
:param packages: All packages from the lockfile to use for identifying dependency relationships.
|
||||||
@ -66,7 +66,11 @@ def find_transients(
|
|||||||
"""
|
"""
|
||||||
from tox_poetry_installer import _poetry
|
from tox_poetry_installer import _poetry
|
||||||
|
|
||||||
def find_deps_of_deps(name: str, searched: Set[str]) -> PackageMap:
|
transients: List[PoetryPackage] = []
|
||||||
|
|
||||||
|
searched: Set[PoetryPackage] = set()
|
||||||
|
|
||||||
|
def find_deps_of_deps(name: str):
|
||||||
searched.add(name)
|
searched.add(name)
|
||||||
|
|
||||||
if name in _poetry.Provider.UNSAFE_PACKAGES:
|
if name in _poetry.Provider.UNSAFE_PACKAGES:
|
||||||
@ -76,9 +80,8 @@ def find_transients(
|
|||||||
tox.reporter.verbosity2(
|
tox.reporter.verbosity2(
|
||||||
f"{constants.REPORTER_PREFIX} Skip {name}: designated unsafe by Poetry"
|
f"{constants.REPORTER_PREFIX} Skip {name}: designated unsafe by Poetry"
|
||||||
)
|
)
|
||||||
return dict()
|
return
|
||||||
|
|
||||||
transients: PackageMap = {}
|
|
||||||
try:
|
try:
|
||||||
package = packages[name]
|
package = packages[name]
|
||||||
except KeyError as err:
|
except KeyError as err:
|
||||||
@ -86,7 +89,7 @@ def find_transients(
|
|||||||
tox.reporter.verbosity2(
|
tox.reporter.verbosity2(
|
||||||
f"{constants.REPORTER_PREFIX} Skip {name}: package is not in lockfile but designated as allowed to be missing"
|
f"{constants.REPORTER_PREFIX} Skip {name}: package is not in lockfile but designated as allowed to be missing"
|
||||||
)
|
)
|
||||||
return dict()
|
return
|
||||||
raise err
|
raise err
|
||||||
|
|
||||||
if not package.python_constraint.allows(constants.PLATFORM_VERSION):
|
if not package.python_constraint.allows(constants.PLATFORM_VERSION):
|
||||||
@ -98,35 +101,29 @@ def find_transients(
|
|||||||
f"{constants.REPORTER_PREFIX} Skip {package}: incompatible platform requirement '{package.platform}' for current platform '{sys.platform}'"
|
f"{constants.REPORTER_PREFIX} Skip {package}: incompatible platform requirement '{package.platform}' for current platform '{sys.platform}'"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
tox.reporter.verbosity2(
|
|
||||||
f"{constants.REPORTER_PREFIX} Including {package} for installation"
|
|
||||||
)
|
|
||||||
transients[name] = package
|
|
||||||
for index, dep in enumerate(package.requires):
|
for index, dep in enumerate(package.requires):
|
||||||
tox.reporter.verbosity2(
|
tox.reporter.verbosity2(
|
||||||
f"{constants.REPORTER_PREFIX} Processing dependency {index + 1}/{len(package.requires)} for {package}: {dep.name}"
|
f"{constants.REPORTER_PREFIX} Processing dependency {index + 1}/{len(package.requires)} for {package}: {dep.name}"
|
||||||
)
|
)
|
||||||
if dep.name not in searched:
|
if dep.name not in searched:
|
||||||
transients.update(find_deps_of_deps(dep.name, searched))
|
find_deps_of_deps(dep.name)
|
||||||
else:
|
else:
|
||||||
tox.reporter.verbosity2(
|
tox.reporter.verbosity2(
|
||||||
f"{constants.REPORTER_PREFIX} Package with name '{dep.name}' has already been processed, skipping"
|
f"{constants.REPORTER_PREFIX} Package with name '{dep.name}' has already been processed, skipping"
|
||||||
)
|
)
|
||||||
|
tox.reporter.verbosity2(
|
||||||
return transients
|
f"{constants.REPORTER_PREFIX} Including {package} for installation"
|
||||||
|
)
|
||||||
searched: Set[str] = set()
|
transients.append(package)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
transients: PackageMap = find_deps_of_deps(
|
find_deps_of_deps(packages[dependency_name].name)
|
||||||
packages[dependency_name].name, searched
|
|
||||||
)
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
if dependency_name in _poetry.Provider.UNSAFE_PACKAGES:
|
if dependency_name in _poetry.Provider.UNSAFE_PACKAGES:
|
||||||
tox.reporter.warning(
|
tox.reporter.warning(
|
||||||
f"{constants.REPORTER_PREFIX} Installing package '{dependency_name}' using Poetry is not supported and will be skipped"
|
f"{constants.REPORTER_PREFIX} Installing package '{dependency_name}' using Poetry is not supported and will be skipped"
|
||||||
)
|
)
|
||||||
return set()
|
return []
|
||||||
|
|
||||||
if any(
|
if any(
|
||||||
delimiter in dependency_name
|
delimiter in dependency_name
|
||||||
@ -140,7 +137,7 @@ def find_transients(
|
|||||||
f"No version of locked dependency '{dependency_name}' found in the project lockfile"
|
f"No version of locked dependency '{dependency_name}' found in the project lockfile"
|
||||||
) from None
|
) from None
|
||||||
|
|
||||||
return set(transients.values())
|
return transients
|
||||||
|
|
||||||
|
|
||||||
def check_preconditions(venv: ToxVirtualEnv, action: ToxAction) -> "_poetry.Poetry":
|
def check_preconditions(venv: ToxVirtualEnv, action: ToxAction) -> "_poetry.Poetry":
|
||||||
@ -181,9 +178,9 @@ def check_preconditions(venv: ToxVirtualEnv, action: ToxAction) -> "_poetry.Poet
|
|||||||
def find_project_dependencies(
|
def find_project_dependencies(
|
||||||
venv: ToxVirtualEnv, poetry: "_poetry.Poetry", packages: PackageMap
|
venv: ToxVirtualEnv, poetry: "_poetry.Poetry", packages: PackageMap
|
||||||
) -> List[PoetryPackage]:
|
) -> List[PoetryPackage]:
|
||||||
"""Install the dependencies of the project package
|
"""Find the root package dependencies
|
||||||
|
|
||||||
Install all primary dependencies of the project package.
|
Recursively identify the root package dependencies
|
||||||
|
|
||||||
:param venv: Tox virtual environment to install the packages to
|
:param venv: Tox virtual environment to install the packages to
|
||||||
:param poetry: Poetry object the packages were sourced from
|
:param poetry: Poetry object the packages were sourced from
|
||||||
@ -212,8 +209,50 @@ def find_project_dependencies(
|
|||||||
|
|
||||||
dependencies: List[PoetryPackage] = []
|
dependencies: List[PoetryPackage] = []
|
||||||
for dep in base_dependencies + extra_dependencies:
|
for dep in base_dependencies + extra_dependencies:
|
||||||
dependencies += find_transients(
|
dependencies += identify_transients(
|
||||||
packages, dep.name.lower(), allow_missing=[poetry.package.name]
|
packages, dep.name.lower(), allow_missing=[poetry.package.name]
|
||||||
)
|
)
|
||||||
|
|
||||||
return dependencies
|
return dependencies
|
||||||
|
|
||||||
|
|
||||||
|
def find_dev_dependencies(
|
||||||
|
poetry: "_poetry.Poetry", packages: PackageMap
|
||||||
|
) -> List[PoetryPackage]:
|
||||||
|
"""Find the dev dependencies
|
||||||
|
|
||||||
|
Recursively identify the Poetry dev dependencies
|
||||||
|
|
||||||
|
:param venv: Tox virtual environment to install the packages to
|
||||||
|
:param poetry: Poetry object the packages were sourced from
|
||||||
|
:param packages: Mapping of package names to the corresponding package object
|
||||||
|
"""
|
||||||
|
dependencies: List[PoetryPackage] = []
|
||||||
|
for dep_name in (
|
||||||
|
poetry.pyproject.data["tool"]["poetry"].get("dev-dependencies", {}).keys()
|
||||||
|
):
|
||||||
|
dependencies += identify_transients(
|
||||||
|
packages, dep_name, allow_missing=[poetry.package.name]
|
||||||
|
)
|
||||||
|
|
||||||
|
return dependencies
|
||||||
|
|
||||||
|
|
||||||
|
def find_env_dependencies(
|
||||||
|
venv: ToxVirtualEnv, poetry: "_poetry.Poetry", packages: PackageMap
|
||||||
|
) -> List[PoetryPackage]:
|
||||||
|
"""Find the environment dependencies
|
||||||
|
|
||||||
|
Recursively identify the dependencies to install for the current environment
|
||||||
|
|
||||||
|
:param venv: Tox virtual environment to install the packages to
|
||||||
|
:param poetry: Poetry object the packages were sourced from
|
||||||
|
:param packages: Mapping of package names to the corresponding package object
|
||||||
|
"""
|
||||||
|
dependencies: List[PoetryPackage] = []
|
||||||
|
for dep in venv.envconfig.locked_deps:
|
||||||
|
dependencies += identify_transients(
|
||||||
|
packages, dep.lower(), allow_missing=[poetry.package.name]
|
||||||
|
)
|
||||||
|
|
||||||
|
return dependencies
|
||||||
|
Loading…
Reference in New Issue
Block a user