Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Install via conda #2554

Merged
merged 58 commits into from
Jul 27, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
fd9eb82
WIP re #39 - run dependency management during repository installation
bwlang Jun 26, 2016
dcb57a6
Need to unnpack kwargs (ping @bwlang)
mvdbeek Jun 26, 2016
de6bbf1
Move get_unique_requirements to shed_util_common
mvdbeek Jun 26, 2016
179d02c
Iterate over all tools in a repository
mvdbeek Jun 26, 2016
f7d5f5d
Fix the parsing logic
mvdbeek Jun 26, 2016
07ed963
Be a bit more defensive about parsing requirements
mvdbeek Jun 26, 2016
f0f6d88
Fix indentation
mvdbeek Jun 26, 2016
e7aaf99
passed a “manual install” argument to the dependency resolve
bwlang Jun 26, 2016
523baf3
Probably better not to have a fake directory here…
bwlang Jun 26, 2016
0252acc
ask for tool dependency installation always.
bwlang Jun 27, 2016
dade96c
this view is not necessary for the current implementation
bwlang Jun 27, 2016
609898b
strangely… we need
bwlang Jun 27, 2016
c59fd5e
resolving style issue
bwlang Jun 27, 2016
7535879
Add api route to fetch all requirements
mvdbeek Jun 28, 2016
1a9e2e7
Continue dependency resolver API routes and start work on displaying…
mvdbeek Jun 29, 2016
672480f
Add first pass for displaying dependency status info
bwlang Jun 29, 2016
a806867
Add name and version to returned dependency
mvdbeek Jun 29, 2016
2a7f67b
Hack in name and version into dictified representation of Dependency …
mvdbeek Jun 29, 2016
4bd0013
add status field with icons
bwlang Jun 29, 2016
ee33ad8
whitespace fix
bwlang Jul 1, 2016
e7f8a13
Address @nsoranzo's review.
mvdbeek Jul 2, 2016
cc085cb
Work on conda install revision
mvdbeek Jul 12, 2016
69480a6
Fix dependency typo
mvdbeek Jul 18, 2016
7a8265b
Make api return more detailed requirements status, move tool_requirem…
mvdbeek Jul 18, 2016
983f4a0
Use list comprehension to generate list of all requirements
mvdbeek Jul 19, 2016
2e7099c
Fix logic error in message
mvdbeek Jul 20, 2016
93579a6
Display whether dependency is exactly resolved (check), inexactly res…
mvdbeek Jul 20, 2016
602ced8
Close docstring example list
mvdbeek Jul 20, 2016
f75a28e
Trigger requirement install for new install method
mvdbeek Jul 20, 2016
414bc87
flake8 fixes
mvdbeek Jul 20, 2016
4eadf05
Add API endpoint to install (resolver) dependencies
mvdbeek Jul 21, 2016
503915f
Correct module description
mvdbeek Jul 21, 2016
db05313
Trigger dependency installation in InstallRepositoryManager
mvdbeek Jul 21, 2016
dcba018
Add docstring to install_dependency
mvdbeek Jul 21, 2016
a8670a2
Revert manual_install mode (unncessary, better to use install_depende…
mvdbeek Jul 21, 2016
6901cb1
Introduce installable_resolver property
mvdbeek Jul 21, 2016
33b83e5
Add install resolver dependency button
mvdbeek Jul 21, 2016
4ed151a
Revert mako hack to always display install_tool_dependency button
mvdbeek Jul 21, 2016
886d074
Make conda verbose install check configurable
mvdbeek Jul 22, 2016
2018632
Only return requirements of type package (for now)
mvdbeek Jul 22, 2016
0b24283
Use resolver.find_dep to determine dependency status
mvdbeek Jul 23, 2016
b59b797
Update INDETERMINATE_DEPENDENCY use to NullDependency instance
mvdbeek Jul 25, 2016
c10d1cb
Remove unused DependencyResolver view from ToolBox
mvdbeek Jul 25, 2016
49dee6a
Flake8 fix
mvdbeek Jul 25, 2016
6b135c6
Drop INDETERMINATE_DEPENDENCY alias
mvdbeek Jul 25, 2016
dd8f8f1
Fix NullDependency use
mvdbeek Jul 25, 2016
b5fad4a
Add api test for conda dependency resolution
mvdbeek Jul 25, 2016
deef480
Add more API tests for resolver dependencies
mvdbeek Jul 25, 2016
e53a81c
Fix syntax, return None if dependency not found
mvdbeek Jul 26, 2016
9917512
Revert svgwrite import, fixed in #2656
mvdbeek Jul 26, 2016
645a891
Remove reference to /tmp
mvdbeek Jul 26, 2016
06e409a
Use continue instead of unused NullDependency
mvdbeek Jul 27, 2016
2a5b043
Simplify conda install logic
mvdbeek Jul 27, 2016
8cca7be
Show conda install option even when no tool_dependencies.xml is avail…
jmchilton Jul 27, 2016
483b66b
Address @nsoranzo's review
mvdbeek Jul 27, 2016
924a913
Mark conda install beta and move after TS options.
jmchilton Jul 27, 2016
88fac71
Merge remote-tracking branch 'bxlab/install_via_conda' into install_v…
jmchilton Jul 27, 2016
12209ab
Merge pull request #56 from jmchilton/install_via_conda
martenson Jul 27, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions config/galaxy.ini.sample
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ paste.app_factory = galaxy.web.buildapp:app_factory
# Set to True to instruct Galaxy to look for and install missing tool
# dependencies before each job runs.
#conda_auto_install = False
# Set to True to perform additional checking of installed conda environment
#conda_verbose_install_check=False
# Set to True to instruct Galaxy to install conda from the web automatically
# if it cannot find a local copy and conda_exec is not configured.
#conda_auto_init = False
Expand Down
22 changes: 22 additions & 0 deletions lib/galaxy/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from galaxy.tools.actions.data_source import DataSourceToolAction
from galaxy.tools.actions.data_manager import DataManagerToolAction
from galaxy.tools.actions.model_operations import ModelOperationToolAction
from galaxy.tools.deps import views
from galaxy.tools.parameters import params_to_incoming, check_param, params_from_strings, params_to_strings, visit_input_values
from galaxy.tools.parameters import output_collect
from galaxy.tools.parameters.basic import (BaseURLToolParameter,
Expand Down Expand Up @@ -108,6 +109,11 @@ def __init__( self, config_filenames, tool_root_dir, app ):
app=app,
)

@property
def all_requirements(self):
reqs = [json.dumps(req, sort_keys=True) for _, tool in self.tools() for req in tool.tool_requirements]
return [json.loads(req) for req in set(reqs)]

@property
def tools_by_id( self ):
# Deprecated method, TODO - eliminate calls to this in test/.
Expand Down Expand Up @@ -310,6 +316,7 @@ def __init__( self, config_file, tool_source, app, guid=None, repository_id=None
global_tool_errors.add_error(config_file, "Tool Loading", e)
raise e
self.history_manager = histories.HistoryManager( app )
self._view = views.DependencyResolversView(app)

@property
def sa_session( self ):
Expand Down Expand Up @@ -1292,6 +1299,21 @@ def installed_tool_dependencies(self):
installed_tool_dependencies = None
return installed_tool_dependencies

@property
def tool_requirements(self):
"""
Return all requiremens of type package
"""
reqs = [req.to_dict() for req in self.requirements if req.type == 'package']
return reqs

@property
def tool_requirements_status(self):
"""
Return a list of dictionaries for all tool dependencies with their associated status
"""
return self._view.get_requirements_status(self.tool_requirements)

def build_redirect_url_params( self, param_dict ):
"""
Substitute parameter values into self.redirect_url_params
Expand Down
12 changes: 6 additions & 6 deletions lib/galaxy/tools/deps/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import logging
log = logging.getLogger( __name__ )

from .resolvers import INDETERMINATE_DEPENDENCY
from .resolvers import NullDependency
from .resolvers.galaxy_packages import GalaxyPackageDependencyResolver
from .resolvers.tool_shed_packages import ToolShedPackageDependencyResolver
from .resolvers.conda import CondaDependencyResolver
Expand Down Expand Up @@ -58,7 +58,7 @@ def dependency_shell_commands( self, requirements, **kwds ):
return []

def find_dep( self, name, version=None, type='package', **kwds ):
return INDETERMINATE_DEPENDENCY
return NullDependency(version=version, name=name)


class DependencyManager( object ):
Expand Down Expand Up @@ -90,7 +90,7 @@ def dependency_shell_commands( self, requirements, **kwds ):
commands = []
for requirement in requirements:
log.debug( "Building dependency shell command for dependency '%s'", requirement.name )
dependency = INDETERMINATE_DEPENDENCY
dependency = NullDependency(version=requirement.version, name=requirement.name)
if requirement.type in [ 'package', 'set_environment' ]:
dependency = self.find_dep( name=requirement.name,
version=requirement.version,
Expand All @@ -115,10 +115,10 @@ def find_dep( self, name, version=None, type='package', **kwds ):
continue
dependency = resolver.resolve( name, version, type, **kwds )
if require_exact and not dependency.exact:
dependency = INDETERMINATE_DEPENDENCY
if dependency != INDETERMINATE_DEPENDENCY:
continue
if not isinstance(dependency, NullDependency):
return dependency
return INDETERMINATE_DEPENDENCY
return NullDependency(version=version, name=name)

def __build_dependency_resolvers( self, conf_file ):
if not conf_file:
Expand Down
9 changes: 6 additions & 3 deletions lib/galaxy/tools/deps/conda_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,11 +322,13 @@ def is_target_available(conda_target, conda_context=None):
return False


def is_conda_target_installed(conda_target, conda_context=None):
def is_conda_target_installed(conda_target, conda_context=None, verbose_install_check=False):
conda_context = _ensure_conda_context(conda_context)
# fail by default
success = False
if conda_context.has_env(conda_target.install_environment):
if not verbose_install_check:
return True
# because export_list directs output to a file we
# need to make a temporary file, not use StringIO
f, package_list_file = tempfile.mkstemp(suffix='.env_packages')
Expand All @@ -342,10 +344,11 @@ def is_conda_target_installed(conda_target, conda_context=None):
return success


def filter_installed_targets(conda_targets, conda_context=None):
def filter_installed_targets(conda_targets, conda_context=None, verbose_install_check=False):
conda_context = _ensure_conda_context(conda_context)
installed = functools.partial(is_conda_target_installed,
conda_context=conda_context)
conda_context=conda_context,
verbose_install_check=verbose_install_check)
return filter(installed, conda_targets)


Expand Down
10 changes: 6 additions & 4 deletions lib/galaxy/tools/deps/resolvers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class DependencyResolver(Dictifiable, object):
# because the repository install context is used in dependency resolution
# so the same requirement tags in different tools will have very different
# resolution.
disabled = False
resolves_simple_dependencies = True
__metaclass__ = ABCMeta

Expand Down Expand Up @@ -71,7 +72,7 @@ def install_dependency(self, name, version, type, **kwds):


class Dependency(Dictifiable, object):
dict_collection_visible_keys = ['dependency_type', 'exact']
dict_collection_visible_keys = ['dependency_type', 'exact', 'name', 'version']
__metaclass__ = ABCMeta

@abstractmethod
Expand All @@ -91,8 +92,9 @@ class NullDependency( Dependency ):
dependency_type = None
exact = True

def __init__(self, version=None, name=None):
self.version = version
self.name = name

def shell_commands( self, requirement ):
return None


INDETERMINATE_DEPENDENCY = NullDependency()
13 changes: 6 additions & 7 deletions lib/galaxy/tools/deps/resolvers/brewed_tool_shed_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
UsesToolDependencyDirMixin,
UsesInstalledRepositoriesMixin,
)
from ..resolvers import DependencyResolver, INDETERMINATE_DEPENDENCY
from ..resolvers import DependencyResolver, NullDependency

log = logging.getLogger(__name__)

Expand All @@ -31,10 +31,9 @@ def __init__(self, dependency_manager, **kwds):

def resolve(self, name, version, type, **kwds):
if type != "package":
return INDETERMINATE_DEPENDENCY

return NullDependency(version=version, name=name)
if version is None:
return INDETERMINATE_DEPENDENCY
return NullDependency(version=version, name=name)

return self._find_tool_dependencies(name, version, type, **kwds)

Expand All @@ -49,7 +48,7 @@ def _find_tool_dependencies(self, name, version, type, **kwds):
if os.path.exists(tool_depenedencies_path):
return self._resolve_from_tool_dependencies_path(name, version, tool_depenedencies_path)

return INDETERMINATE_DEPENDENCY
return NullDependency(version=version, name=name)

def _resolve_from_installed_tool_dependency(self, name, version, installed_tool_dependency):
tool_shed_repository = installed_tool_dependency.tool_shed_repository
Expand All @@ -66,11 +65,11 @@ def _resolve_from_tool_dependencies_path(self, name, version, tool_dependencies_
raw_dependencies = RawDependencies(tool_dependencies_path)
except Exception:
log.debug("Failed to parse dependencies in file %s" % tool_dependencies_path)
return INDETERMINATE_DEPENDENCY
return NullDependency(version=version, name=name)

raw_dependency = raw_dependencies.find(name, version)
if not raw_dependency:
return INDETERMINATE_DEPENDENCY
return NullDependency(version=version, name=name)

recipe_name = build_recipe_name(
package_name=name,
Expand Down
107 changes: 72 additions & 35 deletions lib/galaxy/tools/deps/resolvers/conda.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@

import os

from galaxy.exceptions import NotImplemented

from ..resolvers import (
DependencyResolver,
INDETERMINATE_DEPENDENCY,
NullDependency,
Dependency,
ListableDependencyResolver,
InstallableDependencyResolver,
Expand Down Expand Up @@ -65,6 +63,7 @@ def get_option(name):

conda_exec = get_option("exec")
debug = _string_as_bool(get_option("debug"))
verbose_install_check = _string_as_bool(get_option("verbose_install_check"))
ensure_channels = get_option("ensure_channels")
use_path_exec = get_option("use_path_exec")
if use_path_exec is None:
Expand All @@ -89,53 +88,57 @@ def get_option(name):
auto_install = _string_as_bool(get_option("auto_install"))
copy_dependencies = _string_as_bool(get_option("copy_dependencies"))

if auto_init and not os.path.exists(conda_context.conda_prefix):
if install_conda(conda_context):
raise Exception("Conda installation requested and failed.")
if not os.path.exists(conda_context.conda_prefix):
if auto_init:
if install_conda(conda_context):
self.disabled = True
log.warning("Conda installation requested and failed.")
else:
self.disabled = True
log.warning("Conda not installed and auto-installation disabled.")

self.conda_context = conda_context
self.auto_install = auto_install
self.copy_dependencies = copy_dependencies
self.verbose_install_check = verbose_install_check

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.
if not os.path.isdir(self.conda_context.conda_prefix):
return INDETERMINATE_DEPENDENCY
return NullDependency(version=version, name=name)

if type != "package":
return INDETERMINATE_DEPENDENCY

job_directory = kwds.get("job_directory", None)
if job_directory is None:
log.warning("Conda dependency resolver not sent job directory.")
return INDETERMINATE_DEPENDENCY
return NullDependency(version=version, name=name)

exact = not self.versionless or version is None
if self.versionless:
version = None

conda_target = CondaTarget(name, version=version)
is_installed = is_conda_target_installed(
conda_target, conda_context=self.conda_context
conda_target, conda_context=self.conda_context, verbose_install_check=self.verbose_install_check
)
if not is_installed and self.auto_install:
return_code = install_conda_target(conda_target, conda_context=self.conda_context)
if return_code != 0:
is_installed = False
log.debug('Cleaning up after failed install of {}, {}'.format(name, version))
cleanup_failed_install(conda_target, conda_context=self.conda_context)
else:
# Recheck if installed
is_installed = is_conda_target_installed(
conda_target, conda_context=self.conda_context

job_directory = kwds.get("job_directory", None)
if job_directory is None: # Job directory is None when resolve() called by find_dep()
if is_installed:
return CondaDependency(
False,
os.path.join(self.conda_context.envs_path, conda_target.install_environment),
exact,
name=name,
version=version
)
if not is_installed:
log.debug('Cleaning up after failing to verify installed environment for {}, {}'.format(name, version))
cleanup_failed_install(conda_target, conda_context=self.conda_context)
else:
log.warning("Conda dependency resolver not sent job directory.")
return NullDependency(version=version, name=name)

if not is_installed and self.auto_install:
is_installed = self.install_dependency(name=name, version=version, type=type)

if not is_installed:
return INDETERMINATE_DEPENDENCY
return NullDependency(version=version, name=name)

# Have installed conda_target and job_directory to send it too.
# If dependency is for metadata generation, store environment in conda-metadata-env
Expand All @@ -150,11 +153,14 @@ def resolve(self, name, version, type, **kwds):
copy=self.copy_dependencies,
conda_context=self.conda_context,
)

if not exit_code:
return CondaDepenency(
return CondaDependency(
self.conda_context.activate,
conda_environment,
exact,
name,
version
)
else:
raise Exception("Conda dependency seemingly installed but failed to build job environment.")
Expand All @@ -166,34 +172,65 @@ def list_dependencies(self):
yield self._to_requirement(name, version)

def install_dependency(self, name, version, type, **kwds):
"Returns True on (seemingly) successfull installation"
if type != "package":
raise NotImplemented("Can only install dependencies of type '%s'" % type)
log.warning("Cannot install dependencies of type '%s'" % type)
return False

if self.versionless:
version = None

conda_target = CondaTarget(name, version=version)

if install_conda_target(conda_target, conda_context=self.conda_context):
raise Exception("Failed to install conda recipe.")
is_installed = is_conda_target_installed(
conda_target, conda_context=self.conda_context, verbose_install_check=self.verbose_install_check
)

if is_installed:
return is_installed

return_code = install_conda_target(conda_target, conda_context=self.conda_context)
if return_code != 0:
is_installed = False
else:
# Recheck if installed
is_installed = is_conda_target_installed(
conda_target, conda_context=self.conda_context, verbose_install_check=self.verbose_install_check
)
if not is_installed:
log.debug("Removing failed conda install of {}, version '{}'".format(name, version))
cleanup_failed_install(conda_target, conda_context=self.conda_context)

return is_installed

@property
def prefix(self):
return self.conda_context.conda_prefix


class CondaDepenency(Dependency):
dict_collection_visible_keys = Dependency.dict_collection_visible_keys + ['environment_path']
class CondaDependency(Dependency):
dict_collection_visible_keys = Dependency.dict_collection_visible_keys + ['environment_path', 'name', 'version']
dependency_type = 'conda'

def __init__(self, activate, environment_path, exact):
def __init__(self, activate, environment_path, exact, name=None, version=None):
self.activate = activate
self.environment_path = environment_path
self._exact = exact
self._name = name
self._version = version

@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):
return """[ "$CONDA_DEFAULT_ENV" = "%s" ] || . %s '%s' 2>&1 """ % (
self.environment_path,
Expand Down
Loading