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