mirror of
				https://github.com/enpaul/tox-poetry-installer.git
				synced 2025-11-03 07:39:20 +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:
		@@ -97,23 +97,18 @@ def tox_testenv_install_deps(venv: ToxVirtualEnv, action: ToxAction) -> Optional
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if venv.envconfig.install_dev_deps:
 | 
			
		||||
            dev_deps: List[PoetryPackage] = [
 | 
			
		||||
                dep
 | 
			
		||||
                for dep in package_map.values()
 | 
			
		||||
                if dep not in poetry.locker.locked_repository(False).packages
 | 
			
		||||
            ]
 | 
			
		||||
            dev_deps = utilities.find_dev_dependencies(poetry, package_map)
 | 
			
		||||
            tox.reporter.verbosity1(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Identified {len(dev_deps)} development dependencies to install to env"
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            dev_deps = []
 | 
			
		||||
 | 
			
		||||
        tox.reporter.verbosity1(
 | 
			
		||||
            f"{constants.REPORTER_PREFIX} Identified {len(dev_deps)} development dependencies to install to env"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        env_deps: List[PoetryPackage] = []
 | 
			
		||||
        for dep in venv.envconfig.locked_deps:
 | 
			
		||||
            env_deps += utilities.find_transients(
 | 
			
		||||
                package_map, dep.lower(), allow_missing=[poetry.package.name]
 | 
			
		||||
            tox.reporter.verbosity1(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Env does not install development dependencies, skipping"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        env_deps = utilities.find_env_dependencies(venv, poetry, package_map)
 | 
			
		||||
 | 
			
		||||
        tox.reporter.verbosity1(
 | 
			
		||||
            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}")
 | 
			
		||||
        raise err
 | 
			
		||||
 | 
			
		||||
    dependencies = list(set(dev_deps + env_deps + project_deps))
 | 
			
		||||
    dependencies = dev_deps + env_deps + project_deps
 | 
			
		||||
    action.setactivity(
 | 
			
		||||
        __about__.__title__,
 | 
			
		||||
        f"Installing {len(dependencies)} dependencies from Poetry lock file",
 | 
			
		||||
 
 | 
			
		||||
@@ -48,9 +48,9 @@ def install_to_venv(
 | 
			
		||||
        installer.install(dependency)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def find_transients(
 | 
			
		||||
def identify_transients(
 | 
			
		||||
    packages: PackageMap, dependency_name: str, allow_missing: Sequence[str] = ()
 | 
			
		||||
) -> Set[PoetryPackage]:
 | 
			
		||||
) -> List[PoetryPackage]:
 | 
			
		||||
    """Using a poetry object identify all dependencies of a specific dependency
 | 
			
		||||
 | 
			
		||||
    :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
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
        if name in _poetry.Provider.UNSAFE_PACKAGES:
 | 
			
		||||
@@ -76,9 +80,8 @@ def find_transients(
 | 
			
		||||
            tox.reporter.verbosity2(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Skip {name}: designated unsafe by Poetry"
 | 
			
		||||
            )
 | 
			
		||||
            return dict()
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        transients: PackageMap = {}
 | 
			
		||||
        try:
 | 
			
		||||
            package = packages[name]
 | 
			
		||||
        except KeyError as err:
 | 
			
		||||
@@ -86,7 +89,7 @@ def find_transients(
 | 
			
		||||
                tox.reporter.verbosity2(
 | 
			
		||||
                    f"{constants.REPORTER_PREFIX} Skip {name}: package is not in lockfile but designated as allowed to be missing"
 | 
			
		||||
                )
 | 
			
		||||
                return dict()
 | 
			
		||||
                return
 | 
			
		||||
            raise err
 | 
			
		||||
 | 
			
		||||
        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}'"
 | 
			
		||||
            )
 | 
			
		||||
        else:
 | 
			
		||||
            tox.reporter.verbosity2(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Including {package} for installation"
 | 
			
		||||
            )
 | 
			
		||||
            transients[name] = package
 | 
			
		||||
            for index, dep in enumerate(package.requires):
 | 
			
		||||
                tox.reporter.verbosity2(
 | 
			
		||||
                    f"{constants.REPORTER_PREFIX} Processing dependency {index + 1}/{len(package.requires)} for {package}: {dep.name}"
 | 
			
		||||
                )
 | 
			
		||||
                if dep.name not in searched:
 | 
			
		||||
                    transients.update(find_deps_of_deps(dep.name, searched))
 | 
			
		||||
                    find_deps_of_deps(dep.name)
 | 
			
		||||
                else:
 | 
			
		||||
                    tox.reporter.verbosity2(
 | 
			
		||||
                        f"{constants.REPORTER_PREFIX} Package with name '{dep.name}' has already been processed, skipping"
 | 
			
		||||
                    )
 | 
			
		||||
 | 
			
		||||
        return transients
 | 
			
		||||
 | 
			
		||||
    searched: Set[str] = set()
 | 
			
		||||
            tox.reporter.verbosity2(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Including {package} for installation"
 | 
			
		||||
            )
 | 
			
		||||
            transients.append(package)
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        transients: PackageMap = find_deps_of_deps(
 | 
			
		||||
            packages[dependency_name].name, searched
 | 
			
		||||
        )
 | 
			
		||||
        find_deps_of_deps(packages[dependency_name].name)
 | 
			
		||||
    except KeyError:
 | 
			
		||||
        if dependency_name in _poetry.Provider.UNSAFE_PACKAGES:
 | 
			
		||||
            tox.reporter.warning(
 | 
			
		||||
                f"{constants.REPORTER_PREFIX} Installing package '{dependency_name}' using Poetry is not supported and will be skipped"
 | 
			
		||||
            )
 | 
			
		||||
            return set()
 | 
			
		||||
            return []
 | 
			
		||||
 | 
			
		||||
        if any(
 | 
			
		||||
            delimiter in dependency_name
 | 
			
		||||
@@ -140,7 +137,7 @@ def find_transients(
 | 
			
		||||
            f"No version of locked dependency '{dependency_name}' found in the project lockfile"
 | 
			
		||||
        ) from None
 | 
			
		||||
 | 
			
		||||
    return set(transients.values())
 | 
			
		||||
    return transients
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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(
 | 
			
		||||
    venv: ToxVirtualEnv, poetry: "_poetry.Poetry", packages: PackageMap
 | 
			
		||||
) -> 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 poetry: Poetry object the packages were sourced from
 | 
			
		||||
@@ -212,8 +209,50 @@ def find_project_dependencies(
 | 
			
		||||
 | 
			
		||||
    dependencies: List[PoetryPackage] = []
 | 
			
		||||
    for dep in base_dependencies + extra_dependencies:
 | 
			
		||||
        dependencies += find_transients(
 | 
			
		||||
        dependencies += identify_transients(
 | 
			
		||||
            packages, dep.name.lower(), allow_missing=[poetry.package.name]
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user