mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
Implement resolve_all on Conda dependency resolver.
This is a somewhat substantial reversal in the typical way that the Conda resolver works. Everything related to caching, copying, linking, and building environments on the fly should now only apply if both of the following conditions are met (1) there is more than one requirement tag in a tool and (2) not all of them are resolvable exactly by the Conda resolver. For recipes that don't meet both of these two criteria - the normal case I would suspect going forward - Galaxy will just look for a hashed environment for these requirements built for all the requirements at once whenever the requirements are installed. The new 90% case, such environments should be much less buggy for two primary reasons. - #3299 is solved - in other words Conda is deferred to and if packages have potential conflicts - Conda can choose the right combination of build specifiers to resolve things correctly. - Environments are no longer built on a per-job basis - this means file system problems related to linking and copying aren't really an issue and complexity related to caching can be safely ignored. My guess is we should re-write all the Conda docs to make the other use case seem like a corner case - because hopefully it is now. This commit includes a test tool that wouldn't work without the rewrite I believe.
This commit is contained in:
@@ -364,6 +364,17 @@ def install_conda(conda_context=None):
|
||||
os.remove(script_path)
|
||||
|
||||
|
||||
def install_conda_targets(conda_targets, env_name, conda_context=None):
|
||||
conda_context = _ensure_conda_context(conda_context)
|
||||
conda_context.ensure_channels_configured()
|
||||
create_args = [
|
||||
"--name", env_name, # enviornment for package
|
||||
]
|
||||
for conda_target in conda_targets:
|
||||
create_args.append(conda_target.package_specifier)
|
||||
return conda_context.exec_create(create_args)
|
||||
|
||||
|
||||
def install_conda_target(conda_target, conda_context=None):
|
||||
""" Install specified target into a its own environment.
|
||||
"""
|
||||
@@ -376,10 +387,14 @@ def install_conda_target(conda_target, conda_context=None):
|
||||
return conda_context.exec_create(create_args)
|
||||
|
||||
|
||||
def cleanup_failed_install(conda_target, conda_context=None):
|
||||
def cleanup_failed_install_of_environment(env, conda_context=None):
|
||||
conda_context = _ensure_conda_context(conda_context)
|
||||
if conda_context.has_env(conda_target.install_environment):
|
||||
conda_context.exec_remove([conda_target.install_environment])
|
||||
if conda_context.has_env(env):
|
||||
conda_context.exec_remove([env])
|
||||
|
||||
|
||||
def cleanup_failed_install(conda_target, conda_context=None):
|
||||
cleanup_failed_install_of_environment(conda_target.install_environment, conda_context=conda_context)
|
||||
|
||||
|
||||
def best_search_result(conda_target, conda_context=None, channels_override=None):
|
||||
|
||||
@@ -11,10 +11,13 @@ import galaxy.tools.deps.installable
|
||||
from ..conda_util import (
|
||||
build_isolated_environment,
|
||||
cleanup_failed_install,
|
||||
cleanup_failed_install_of_environment,
|
||||
CondaContext,
|
||||
CondaTarget,
|
||||
hash_conda_packages,
|
||||
install_conda,
|
||||
install_conda_target,
|
||||
install_conda_targets,
|
||||
installed_conda_targets,
|
||||
is_conda_target_installed,
|
||||
USE_PATH_EXEC_DEFAULT,
|
||||
@@ -102,6 +105,71 @@ class CondaDependencyResolver(DependencyResolver, ListableDependencyResolver, In
|
||||
def clean(self, **kwds):
|
||||
return self.conda_context.exec_clean()
|
||||
|
||||
def install_all(self, conda_targets):
|
||||
env = self.merged_environment_name(conda_targets)
|
||||
return_code = install_conda_targets(conda_targets, env, conda_context=self.conda_context)
|
||||
if return_code != 0:
|
||||
is_installed = False
|
||||
else:
|
||||
# Recheck if installed
|
||||
is_installed = self.conda_context.has_env(env)
|
||||
|
||||
if not is_installed:
|
||||
log.debug("Removing failed conda install of {}".format(str(conda_targets)))
|
||||
cleanup_failed_install_of_environment(env, conda_context=self.conda_context)
|
||||
|
||||
return is_installed
|
||||
|
||||
def resolve_all(self, requirements, **kwds):
|
||||
if len(requirements) == 0:
|
||||
return False
|
||||
|
||||
if not os.path.isdir(self.conda_context.conda_prefix):
|
||||
return False
|
||||
|
||||
for requirement in requirements:
|
||||
if requirement.type != "package":
|
||||
return False
|
||||
|
||||
conda_targets = []
|
||||
for requirement in requirements:
|
||||
version = requirement.version
|
||||
if self.versionless:
|
||||
version = None
|
||||
|
||||
conda_targets.append(CondaTarget(requirement.name, version=version))
|
||||
|
||||
preserve_python_environment = kwds.get("preserve_python_environment", False)
|
||||
|
||||
env = self.merged_environment_name(conda_targets)
|
||||
dependencies = []
|
||||
|
||||
is_installed = self.conda_context.has_env(env)
|
||||
if not is_installed and self.auto_install:
|
||||
is_installed = self.install_all(conda_targets)
|
||||
|
||||
if is_installed:
|
||||
for requirement in requirements:
|
||||
dependency = MergedCondaDependency(
|
||||
self.conda_context,
|
||||
self.conda_context.env_path(env),
|
||||
exact=self.versionless or requirement.version is None,
|
||||
name=requirement.name,
|
||||
version=requirement.version,
|
||||
preserve_python_environment=preserve_python_environment,
|
||||
)
|
||||
dependencies.append(dependency)
|
||||
|
||||
return dependencies
|
||||
|
||||
def merged_environment_name(self, conda_targets):
|
||||
if len(conda_targets) > 1:
|
||||
# For continuity with mulled containers this is kind of nice.
|
||||
return "mulled-v1-%s" % hash_conda_packages(conda_targets)
|
||||
else:
|
||||
assert len(conda_targets) == 1
|
||||
return conda_targets[0].install_environment
|
||||
|
||||
def resolve(self, name, version, type, **kwds):
|
||||
# Check for conda just not being there, this way we can enable
|
||||
# conda by default and just do nothing in not configured.
|
||||
@@ -193,6 +261,48 @@ class CondaDependencyResolver(DependencyResolver, ListableDependencyResolver, In
|
||||
return self.conda_context.conda_prefix
|
||||
|
||||
|
||||
class MergedCondaDependency(Dependency):
|
||||
dict_collection_visible_keys = Dependency.dict_collection_visible_keys + ['environment_path', 'name', 'version']
|
||||
dependency_type = 'conda'
|
||||
|
||||
def __init__(self, conda_context, environment_path, exact, name=None, version=None, preserve_python_environment=False):
|
||||
self.activate = conda_context.activate
|
||||
self.conda_context = conda_context
|
||||
self.environment_path = environment_path
|
||||
self._exact = exact
|
||||
self._name = name
|
||||
self._version = version
|
||||
self.cache_path = None
|
||||
self._preserve_python_environment = preserve_python_environment
|
||||
|
||||
@property
|
||||
def exact(self):
|
||||
return self._exact
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self._version
|
||||
|
||||
def shell_commands(self, requirement):
|
||||
if self._preserve_python_environment:
|
||||
# On explicit testing the only such requirement I am aware of is samtools - and it seems to work
|
||||
# fine with just appending the PATH as done below. Other tools may require additional
|
||||
# variables in the future.
|
||||
return """export PATH=$PATH:'%s/bin' """ % (
|
||||
self.environment_path,
|
||||
)
|
||||
else:
|
||||
return """[ "$CONDA_DEFAULT_ENV" = "%s" ] || . %s '%s' > conda_activate.log 2>&1 """ % (
|
||||
self.environment_path,
|
||||
self.activate,
|
||||
self.environment_path
|
||||
)
|
||||
|
||||
|
||||
class CondaDependency(Dependency):
|
||||
dict_collection_visible_keys = Dependency.dict_collection_visible_keys + ['environment_path', 'name', 'version']
|
||||
dependency_type = 'conda'
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<tool id="mulled_example_conflict" name="mulled_example_conflict" version="0.1.0">
|
||||
<!-- Tool can't be resolved with older style Conda resolution - all requirements
|
||||
must be installed together to ensure there is no conflict. -->
|
||||
<command detect_errors="exit_code"><![CDATA[
|
||||
lumpy 2>&1 | grep -q structural
|
||||
]]></command>
|
||||
<requirements>
|
||||
<requirement type="package" version="0.2.12">lumpy-sv</requirement>
|
||||
<requirement type="package" version="1.11.2">numpy</requirement>
|
||||
</requirements>
|
||||
<inputs>
|
||||
<param name="input1" type="text" value="The value" />
|
||||
</inputs>
|
||||
<outputs>
|
||||
<data name="out_file1" format="txt" />
|
||||
</outputs>
|
||||
</tool>
|
||||
@@ -123,6 +123,7 @@
|
||||
</section>
|
||||
|
||||
<tool file="mulled_example_multi_1.xml" />
|
||||
<tool file="mulled_example_conflict.xml" />
|
||||
|
||||
<tool file="simple_constructs.yml" />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user