1# Licensed to the Apache Software Foundation (ASF) under one 2# or more contributor license agreements. See the NOTICE file 3# distributed with this work for additional information 4# regarding copyright ownership. The ASF licenses this file 5# to you under the Apache License, Version 2.0 (the 6# "License"); you may not use this file except in compliance 7# with the License. You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, 12# software distributed under the License is distributed on an 13# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14# KIND, either express or implied. See the License for the 15# specific language governing permissions and limitations 16# under the License. 17 18import os 19import shlex 20import shutil 21import subprocess 22 23from .logger import logger, ctx 24 25 26def default_bin(name, default): 27 assert(default) 28 env_name = "ARCHERY_{0}_BIN".format(default.upper()) 29 return name if name else os.environ.get(env_name, default) 30 31 32# Decorator running a command and returning stdout 33class capture_stdout: 34 def __init__(self, strip=False, listify=False): 35 self.strip = strip 36 self.listify = listify 37 38 def __call__(self, f): 39 def strip_it(x): 40 return x.strip() if self.strip else x 41 42 def list_it(x): 43 return x.decode('utf-8').splitlines() if self.listify else x 44 45 def wrapper(*argv, **kwargs): 46 # Ensure stdout is captured 47 kwargs["stdout"] = subprocess.PIPE 48 return list_it(strip_it(f(*argv, **kwargs).stdout)) 49 return wrapper 50 51 52class Command: 53 """ 54 A runnable command. 55 56 Class inheriting from the Command class must provide the bin 57 property/attribute. 58 """ 59 60 def __init__(self, bin): 61 self.bin = bin 62 63 def run(self, *argv, **kwargs): 64 assert hasattr(self, "bin") 65 invocation = shlex.split(self.bin) 66 invocation.extend(argv) 67 68 for key in ["stdout", "stderr"]: 69 # Preserve caller intention, otherwise silence 70 if key not in kwargs and ctx.quiet: 71 kwargs[key] = subprocess.PIPE 72 73 # Prefer safe by default 74 if "check" not in kwargs: 75 kwargs["check"] = True 76 77 logger.debug("Executing `{}`".format(invocation)) 78 return subprocess.run(invocation, **kwargs) 79 80 @property 81 def available(self): 82 """ 83 Indicate if the command binary is found in PATH. 84 """ 85 binary = shlex.split(self.bin)[0] 86 return shutil.which(binary) is not None 87 88 def __call__(self, *argv, **kwargs): 89 return self.run(*argv, **kwargs) 90 91 92class CommandStackMixin: 93 def run(self, *argv, **kwargs): 94 stacked_args = self.argv + argv 95 return super(CommandStackMixin, self).run(*stacked_args, **kwargs) 96 97 98class Bash(Command): 99 def __init__(self, bash_bin=None): 100 self.bin = default_bin(bash_bin, "bash") 101