diff --git a/lib/galaxy/tools/deps/brew_exts.py b/lib/galaxy/tools/deps/brew_exts.py index fdf543b964a..763bac60697 100755 --- a/lib/galaxy/tools/deps/brew_exts.py +++ b/lib/galaxy/tools/deps/brew_exts.py @@ -28,6 +28,7 @@ import glob import os import re import sys +import string import subprocess WHITESPACE_PATTERN = re.compile("[\s]+") @@ -43,6 +44,7 @@ NO_BREW_ERROR_MESSAGE = "Could not find brew on PATH, please place on path or pa CANNOT_DETERMINE_TAP_ERROR_MESSAGE = "Cannot determine tap of specified recipe - please use fully qualified recipe (e.g. homebrew/science/samtools)." VERBOSE = False RELAXED = False +BREW_ARGS = [] class BrewContext(object): @@ -104,6 +106,7 @@ class RecipeContext(object): def main(): global VERBOSE global RELAXED + global BREW_ARGS parser = argparse.ArgumentParser(description=DESCRIPTION) parser.add_argument("--brew", help="Path to linuxbrew 'brew' executable to target") actions = ["vinstall", "vuninstall", "vdeps", "vinfo", "env"] @@ -114,11 +117,13 @@ def main(): parser.add_argument('version', metavar='version', help="Version for action (e.g. 0.1.19).") parser.add_argument('--relaxed', action='store_true', help="Relaxed processing - for instance allow use of env on non-vinstall-ed recipes.") parser.add_argument('--verbose', action='store_true', help="Verbose output") + parser.add_argument('restargs', nargs=argparse.REMAINDER) args = parser.parse_args() if args.verbose: VERBOSE = True if args.relaxed: RELAXED = True + BREW_ARGS = args.restargs if not action: action = args.action brew_context = BrewContext(args) @@ -159,7 +164,7 @@ class CommandLineException(Exception): return self.message -def versioned_install(recipe_context, package=None, version=None): +def versioned_install(recipe_context, package=None, version=None, installed_deps=[]): if package is None: package = recipe_context.recipe version = recipe_context.version @@ -176,10 +181,15 @@ def versioned_install(recipe_context, package=None, version=None): versioned = version_info[2] if versioned: dep_to_version[dep] = dep_version + if dep in installed_deps: + continue versioned_install(recipe_context, dep, dep_version) + installed_deps.append(dep) else: # Install latest. dep_to_version[dep] = None + if dep in installed_deps: + continue unversioned_install(dep) try: for dep in deps: @@ -198,7 +208,16 @@ def versioned_install(recipe_context, package=None, version=None): } deps_metadata.append(dep_metadata) - brew_execute(["install", package]) + cellar_root = recipe_context.brew_context.homebrew_cellar + cellar_path = recipe_context.cellar_path + env_actions = build_env_actions(deps_metadata, cellar_root, cellar_path, custom_only=True) + env = EnvAction.build_env(env_actions) + args = ["install"] + if VERBOSE: + args.append("--verbose") + args.extend(BREW_ARGS) + args.append(package) + brew_execute(args, env=env) deps = brew_execute(["deps", package]) deps = [d.strip() for d in deps.split("\n") if d] metadata = { @@ -278,10 +297,10 @@ def attempt_unlink(package): pass -def brew_execute(args): +def brew_execute(args, env=None): os.environ["HOMEBREW_NO_EMOJI"] = "1" # simplify brew parsing. cmds = ["brew"] + args - return execute(cmds) + return execute(cmds, env=env) def build_env_statements_from_recipe_context(recipe_context, **kwds): @@ -290,11 +309,20 @@ def build_env_statements_from_recipe_context(recipe_context, **kwds): return env_statements -def build_env_statements(cellar_root, cellar_path, relaxed=None): +def build_env_statements(cellar_root, cellar_path, relaxed=None, custom_only=False): deps = load_versioned_deps(cellar_path, relaxed=relaxed) + actions = build_env_actions(deps, cellar_root, cellar_path, relaxed, custom_only) + env_statements = [] + for action in actions: + env_statements.extend(action.to_statements()) + return "\n".join(env_statements) + + +def build_env_actions(deps, cellar_root, cellar_path, relaxed=None, custom_only=False): path_appends = [] ld_path_appends = [] + actions = [] def handle_keg(cellar_path): bin_path = os.path.join(cellar_path, "bin") @@ -303,6 +331,14 @@ def build_env_statements(cellar_root, cellar_path, relaxed=None): lib_path = os.path.join(cellar_path, "lib") if os.path.isdir(lib_path): ld_path_appends.append(lib_path) + env_path = os.path.join(cellar_path, "platform_environment.json") + if os.path.exists(env_path): + with open(env_path, "r") as f: + env_metadata = json.load(f) + if "actions" in env_metadata: + def to_action(desc): + return EnvAction(cellar_path, desc) + actions.extend(map(to_action, env_metadata["actions"])) for dep in deps: package = dep['name'] @@ -311,14 +347,54 @@ def build_env_statements(cellar_root, cellar_path, relaxed=None): handle_keg( dep_cellar_path ) handle_keg( cellar_path ) - env_statements = [] - if path_appends: - env_statements.append("PATH=" + ":".join(path_appends) + ":$PATH") - env_statements.append("export PATH") - if ld_path_appends: - env_statements.append("LD_LIBRARY_PATH=" + ":".join(ld_path_appends) + ":$LD_LIBRARY_PATH") - env_statements.append("export LD_LIBRARY_PATH") - return "\n".join(env_statements) + if not custom_only: + if path_appends: + actions.append(EnvAction(cellar_path, {"action": "prepend", "variable": "PATH", "value": ":".join(path_appends)})) + if ld_path_appends: + actions.append(EnvAction(cellar_path, {"action": "prepend", "variable": "LD_LIBRARY_PATH", "value": ":".join(path_appends)})) + return actions + + +class EnvAction(object): + + def __init__(self, keg_root, action_description): + self.variable = action_description["variable"] + self.action = action_description["action"] + self.value = string.Template(action_description["value"]).safe_substitute({ + 'KEG_ROOT': keg_root, + }) + + @staticmethod + def build_env(env_actions): + new_env = os.environ.copy() + map(lambda env_action: env_action.modify_environ(new_env), env_actions) + return new_env + + def modify_environ(self, environ): + if self.action == "set" or not environ.get(self.variable, ""): + environ[self.variable] = self.__eval("${value}") + elif self.action == "prepend": + environ[self.variable] = self.__eval("${value}:%s" % environ[self.variable]) + else: + environ[self.variable] = self.__eval("%s:${value}" % environ[self.variable]) + + def __eval(self, template): + return string.Template(template).safe_substitute( + variable=self.variable, + value=self.value, + ) + + def to_statements(self): + if self.action == "set": + template = '''${variable}="${value}"''' + elif self.action == "prepend": + template = '''${variable}="${value}:$$${variable}"''' + else: + template = '''${variable}="$$${variable}:${value}"''' + return [ + self.__eval(template), + "export %s" % self.variable + ] @contextlib.contextmanager @@ -350,8 +426,15 @@ def git_execute(args): return execute(cmds) -def execute(cmds): - p = subprocess.Popen(cmds, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE) +def execute(cmds, env=None): + subprocess_kwds = dict( + shell=False, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + if env: + subprocess_kwds["env"] = env + p = subprocess.Popen(cmds, **subprocess_kwds) #log = p.stdout.read() global VERBOSE stdout, stderr = p.communicate() @@ -363,7 +446,10 @@ def execute(cmds): def brew_deps(package): - stdout = brew_execute(["deps", package]) + args = ["deps"] + args.extend(BREW_ARGS) + args.append(package) + stdout = brew_execute(args) return [p.strip() for p in stdout.split("\n") if p]