diff --git a/bake/bakefile.py b/bake/bakefile.py index 283877c..efa8fe6 100644 --- a/bake/bakefile.py +++ b/bake/bakefile.py @@ -27,13 +27,14 @@ class Bakefile: if not os.path.exists(path): raise NoBakefileFound() - self.cache = Cache(bf=self, debug=self.debug) + self.skip_cache = Cache(bf=self, namespace="skips", debug=self.debug) + self.env_cache = Cache(bf=self, namespace="env.allowed", debug=self.debug) # Set environment variables for 'bake's that run underneath of us. os.environ["BAKE_SKIP_DONE"] = "1" os.environ["BAKE_SILENT"] = "1" os.environ["PYTHONUNBUFFERED"] = "1" - os.environ["BAKEFILE_PATH"] = self.path + os.environ["BAKEFILE"] = self.path self.chunks self._tasks = None @@ -141,6 +142,7 @@ class Bakefile: root=os.getcwd(), max_depth=4, topdown=False, + **kwargs, ): """Returns the path of a Bakefile in parent directories.""" @@ -149,7 +151,7 @@ class Bakefile: if i > max_depth: break elif filename in f: - return Class(path=os.path.join(c, filename)) + return Class(path=os.path.join(c, filename), **kwargs) i += 1 raise NoBakefileFound(f"No {filename} found!") @@ -347,13 +349,13 @@ class TaskFilter(BaseAction): return key = sha256(key.encode("utf-8")).hexdigest() - old_hash = str(self.bf.cache[key]) + old_hash = str(self.bf.skip_cache[key]) # Get the current filestate hashsum. with open(key_path, "r") as f: current_hash = sha256(f.read().encode("utf-8")).hexdigest() - self.bf.cache[key] = current_hash + self.bf.skip_cache[key] = current_hash if old_hash == current_hash: self.do_skip = True diff --git a/bake/cache.py b/bake/cache.py index b4d5803..2e8e79a 100644 --- a/bake/cache.py +++ b/bake/cache.py @@ -11,11 +11,12 @@ class Cache: prefix = PREFIX seperator = SEPERATOR - def __init__(self, *, bf, debug=False, enabled=True): + def __init__(self, *, bf, namespace="hashes", debug=False, enabled=True): self.bf = bf self.enabled = enabled self.debug = debug + self.namespace = namespace try: # Assert git exists, and appears functioning. @@ -46,7 +47,7 @@ class Cache: return f"" def _key_for_hashes(self, key): - return self.seperator.join((self.prefix, "hashes", key)) + return self.seperator.join((self.prefix, self.namespace, key)) def clear(self): for key in self: @@ -65,8 +66,9 @@ class Cache: c = delegator.run(cmd) for result in c.out.split("\n"): if result.startswith(self.prefix): - print(result.split("=", -1)[0]) - yield result.split("=", -1)[0] + yield result.split("=", -1)[0][ + len(self.prefix + "." + self.namespace + ".") : + ] def __getitem__(self, k): key = self._key_for_hashes(k) @@ -92,7 +94,8 @@ class Cache: return c.ok def __delitem__(self, k): - cmd = f"git config --local --unset {k}" + key = self._key_for_hashes(k) + cmd = f"git config --local --unset {key}" if self.debug: click.echo(f" {click.style('$', fg='green')} {cmd}", err=True) diff --git a/bake/cli.py b/bake/cli.py index 216e031..494a123 100644 --- a/bake/cli.py +++ b/bake/cli.py @@ -82,7 +82,7 @@ def echo_json(obj): "--bakefile", "-b", default="__BAKEFILE__", - envvar="BAKEFILE_PATH", + envvar="BAKEFILE", nargs=1, type=click.Path(), help="The Bakefile to use.", @@ -95,7 +95,13 @@ def echo_json(obj): help="Lists available tasks (and their dependencies).", ) @click.option( - "--clear-cache", default=False, is_flag=True, help="Clears the cache (e.g. @skip)." + "--clear-skips", default=False, is_flag=True, help="Clears the skip cache." +) +@click.option( + "--clear-envs", + default=False, + is_flag=True, + help="Clears the allowed environment variable cache.", ) @click.option( "--levels", @@ -114,7 +120,6 @@ def echo_json(obj): "--allow", default=False, nargs=1, - multiple=True, hidden=False, help="Whitelist an environment variable for use.", ) @@ -189,7 +194,8 @@ def entrypoint( allow, _json, no_deps, - clear_cache, + clear_skips, + clear_envs, interactive, yes, help, @@ -204,9 +210,6 @@ def entrypoint( if _json or source: silent = True - # Allow explicitly–passed environment variables. - SAFE_ENVIRONS.extend(allow) - # Enable list functionality, by default. if task == "__LIST_ALL__": _list = True @@ -215,9 +218,9 @@ def entrypoint( # Establish the Bakefile. try: bf = ( - Bakefile.find(root=".", filename="Bakefile") + Bakefile.find(root=".", filename="Bakefile", debug=debug) if bakefile == "__BAKEFILE__" - else Bakefile(path=bakefile) + else Bakefile(path=bakefile, debug=debug) ) except NoBakefileFound: @@ -225,12 +228,22 @@ def entrypoint( do_help(1) sys.exit(0) + if allow: + bf.env_cache[allow] = 1 + + # Clear the cache, if asked to do so. + if clear_envs: + bf.env_cache.clear() + + # Allow explicitly–passed environment variables. + SAFE_ENVIRONS.extend([g.upper() for g in bf.env_cache]) + if debug: click.echo(f" + Bakefile: {bf.path}", err=True) # Clear the cache, if asked to do so. - if clear_cache: - bf.cache.clear() + if clear_skips: + bf.skip_cache.clear() # --source (internal API) if source: diff --git a/bake/constants.py b/bake/constants.py index 75a5685..44cc800 100644 --- a/bake/constants.py +++ b/bake/constants.py @@ -9,7 +9,7 @@ SAFE_ENVIRONS = [ "USER", "TERM", "VIRTUAL_ENV", - "BAKEFILE_PATH", + "BAKEFILE", "PYTHONUNBUFFERED", "PYTHONDONTWRITEBYTECODE", "BAKE_SILENT", diff --git a/bake/scripts/notred.py b/bake/scripts/notred.py index 582438f..5a229c3 100644 --- a/bake/scripts/notred.py +++ b/bake/scripts/notred.py @@ -1,6 +1,7 @@ import sys import click +from click.utils import strip_ansi @click.command(context_settings=dict(help_option_names=["-h", "--help"])) @@ -13,4 +14,4 @@ def entrypoint(s, *, err): s = s.rstrip() - click.echo(s) + click.echo(strip_ansi(s)) diff --git a/bake/scripts/step.py b/bake/scripts/step.py index 9c61f04..b240aad 100644 --- a/bake/scripts/step.py +++ b/bake/scripts/step.py @@ -12,18 +12,24 @@ colorama.init(strip=False) @click.command(context_settings=dict(help_option_names=["-h", "--help"])) @click.argument("s", type=click.STRING, default=False, required=False) @click.option( - "--read-stderr", is_flag=True, type=click.BOOL, default=True, help="Read stderr." + "--read-stderr", is_flag=True, type=click.BOOL, default=False, help="Read stderr." +) +@click.option( + "--no-color", is_flag=True, type=click.BOOL, default=False, help="Read stderr." ) @click.option("--char", nargs=1, type=click.STRING, default="+", help="Prefix char.") @click.option( "--color", nargs=1, type=click.STRING, default="yellow", help="Color to use." ) -def entrypoint(s, *, char, read_stderr, color): +def entrypoint(s, *, char, read_stderr, no_color, color): pipe = sys.stdin if not read_stderr else sys.stderr if s is False: s = pipe.read() + if no_color: + color = "NOTACOLOR" + for line in s.strip().split("\n"): try: title = str(click.style(line, fg=color)) diff --git a/tests/_common.sh b/tests/_common.sh new file mode 100644 index 0000000..9264c37 --- /dev/null +++ b/tests/_common.sh @@ -0,0 +1,8 @@ +timeout() { + time=$1 + command="/bin/sh -c \"$2\"" + + expect -c "set echo \"-noecho\"; set timeout $time; spawn -noecho $command; expect timeout { exit 0 } eof { exit 1 }" + +} +export timeout diff --git a/tests/args.Bakefile b/tests/args.Bakefile new file mode 100644 index 0000000..c20012f --- /dev/null +++ b/tests/args.Bakefile @@ -0,0 +1,4 @@ +argv: + set -u + echo "${@}" + echo "${KEY}" diff --git a/tests/args.bats b/tests/args.bats new file mode 100644 index 0000000..5d550db --- /dev/null +++ b/tests/args.bats @@ -0,0 +1,15 @@ +@test "arguments don't work (set -u)" { + run bake -b args.Bakefile argv + [ "${status}" -eq 1 ] +} + +@test "arguments do work [argv]" { + run bake --silent -b args.Bakefile argv 1 2 KEY=VALUE 3 + [[ "${lines[0]}" == *"1 2 3"* ]] +} + + +@test "arguments do work [environ]" { + run bake --silent -b args.Bakefile argv 1 2 KEY=VALUE 3 + [[ "${lines[1]}" == *"VALUE"* ]] +} diff --git a/tests/basics.Bakefile b/tests/basics.Bakefile new file mode 100644 index 0000000..8b971b3 --- /dev/null +++ b/tests/basics.Bakefile @@ -0,0 +1,18 @@ +//two: +one/two/three/four: +one/two/: + +echo: + echo ${1} + +echo/dep: dep + bake echo ${1} | red + +dep: + echo 'Installing dep…' + exit 0 + +fail: + exit 1 + +deps/fail: fail diff --git a/tests/basics.bats b/tests/basics.bats new file mode 100755 index 0000000..7c1293f --- /dev/null +++ b/tests/basics.bats @@ -0,0 +1,72 @@ +#!/usr/bin/env bats + +@test "bake --h" { + run bake -b basics.Bakefile --help + [ "${lines[0]}" = "Usage: [OPTIONS] [TASK] [ARGUMENTS]..." ] +} + +@test "bake --help" { + run bake -b basics.Bakefile --help + [ "${lines[0]}" = "Usage: [OPTIONS] [TASK] [ARGUMENTS]..." ] +} + +@test "bake --json" { + run bake -b basics.Bakefile --json + [ "${#lines[@]}" -eq 23 ] +} + +@test "bake --json --levels 0" { + run bake -b basics.Bakefile --json --levels 0 + [ "${#lines[@]}" -eq 3 ] +} + +@test "bake --levels 0" { + run bake -b basics.Bakefile --levels 0 + [ "${lines[0]}" = "" ] +} + +@test "bake --levels 1" { + run bake -b basics.Bakefile --levels 1 + [ "${#lines[@]}" -eq 4 ] +} + +@test "bake --levels 2" { + run bake -b basics.Bakefile --levels 3 + [ "${#lines[@]}" -eq 10 ] +} + +@test "bake --levels 3" { + run bake -b basics.Bakefile --levels 3 + [ "${#lines[@]}" -eq 10 ] +} + +@test "bake --levels 4" { + run bake -b basics.Bakefile --levels 4 + [ "${#lines[@]}" -eq 10 ] +} + + +@test "bake fails on 'exit 1'" { + run bake -b basics.Bakefile fail + [ "${status}" -eq 1 ] +} + +@test "bake fails on sub–task 'exit 1'" { + run bake -b basics.Bakefile deps/fail + [ "${status}" -eq 1 ] +} + +@test "bake runs tasks" { + run bake -b basics.Bakefile echo + [ "${status}" -eq 0 ] +} + +@test "bake runs sub-tasks" { + run bake -b basics.Bakefile echo/dep + [ "${status}" -eq 0 ] +} + +@test "bake --no-deps" { + run bake -b basics.Bakefile deps/fail --no-deps + [ "${status}" -eq 0 ] +} diff --git a/tests/cache.Bakefile b/tests/cache.Bakefile new file mode 100644 index 0000000..656bf24 --- /dev/null +++ b/tests/cache.Bakefile @@ -0,0 +1,4 @@ +task: skipme + exit 0 +skipme: @skip:key=cache.Bakefile + exit 0 diff --git a/tests/cache.bats b/tests/cache.bats new file mode 100644 index 0000000..9413421 --- /dev/null +++ b/tests/cache.bats @@ -0,0 +1,19 @@ + +@test "skips clear" { + bake -b cache.Bakefile --clear-skips +} + +@test "cache runs" { + run bake -b cache.Bakefile task + [[ $output != *Skipping* ]] +} + +@test "cache skips" { + run bake -b cache.Bakefile task + [[ $output == *Skipping* ]] +} + +@test "skip skips" { + run bake -b cache.Bakefile --no-deps task + [[ $output != *Skipping* ]] +} diff --git a/tests/confirm.Bakefile b/tests/confirm.Bakefile new file mode 100644 index 0000000..80adfe6 --- /dev/null +++ b/tests/confirm.Bakefile @@ -0,0 +1,5 @@ +secure: @confirm + exit 0 + +secure/real: @confirm:secure + exit 1 diff --git a/tests/confirm.bats b/tests/confirm.bats new file mode 100755 index 0000000..7728e38 --- /dev/null +++ b/tests/confirm.bats @@ -0,0 +1,17 @@ +source ./_common.sh + +@test "confirm works" { + timeout 1 "bake -b confirm.Bakefile secure" +} + +@test "confirm --yes works" { + bake -b confirm.Bakefile secure --yes +} + +@test "confirm:secure works" { + timeout 1 "bake -b confirm.Bakefile secure/real" +} + +@test "confirm:secure --yes don't work" { + timeout 1 "bake -b confirm.Bakefile secure/real --yes" +} diff --git a/tests/env.Bakefile b/tests/env.Bakefile new file mode 100644 index 0000000..8067dc5 --- /dev/null +++ b/tests/env.Bakefile @@ -0,0 +1,2 @@ +env: + env diff --git a/tests/env.bats b/tests/env.bats new file mode 100644 index 0000000..70d8329 --- /dev/null +++ b/tests/env.bats @@ -0,0 +1,19 @@ +export HELLO=WORLD + + +@test "env cache clear" { + bake -b env.Bakefile --clear-envs env +} + + +@test "removal of environment untrusted variables" { + run bake -b env.Bakefile env + [[ "${output}" != *HELLO=WORLD* ]] +} + + +@test "allowance of environment untrusted variables" { + run bake --allow HELLO + run bake -b env.Bakefile env + [[ "${output}" == *"WORLD"* ]] +} diff --git a/tests/python.Bakefile b/tests/python.Bakefile new file mode 100644 index 0000000..ef5cd33 --- /dev/null +++ b/tests/python.Bakefile @@ -0,0 +1,3 @@ +python: + #!/usr/bin/env python + print('not bash') diff --git a/tests/python.bats b/tests/python.bats new file mode 100644 index 0000000..ea1576e --- /dev/null +++ b/tests/python.bats @@ -0,0 +1,4 @@ +@test "python" { + run bake --silent -b python.Bakefile python + [[ "${lines[0]}" == "not bash" ]] +} diff --git a/tests/utils.bats b/tests/utils.bats new file mode 100755 index 0000000..76e81c8 --- /dev/null +++ b/tests/utils.bats @@ -0,0 +1,94 @@ +#!/usr/bin/env bats + +declare -a COLORS=('white', 'red', 'green', 'blue', 'cyan', 'purple', 'magenta') + +@test "$(red red) --help" { + run red --help + [ "${lines[0]}" = "Usage: red [OPTIONS] [S]" ] +} + +@test "$(red red) \${s}" { + run red test + [[ "${lines[0]}" == *"test"* ]] +} + +@test "echo \${s} | $(red red)" { + output=$(echo test | red) + [[ "$output" == *"test"* ]] +} + + +@test "$(red red) --fg \${COLOR}" { + for COLOR in "${COLORS[@]}"; do + output=$(echo test | red --fg ${COLOR} ) + [[ "$output" == *"test"* ]] + done +} + +@test "$(red red) --fg \${COLOR} --bold" { + for COLOR in "${COLORS[@]}"; do + output=$(echo test | red --fg ${COLOR} --bold) + [[ "$output" == *"test"* ]] + done +} + +@test "$(red red) --bg \${COLOR}" { + for COLOR in "${COLORS[@]}"; do + output=$(echo test | red --bg ${COLOR} ) + [[ "$output" == *"test"* ]] + done +} + +@test "$(red red) --bg \${COLOR} --bold" { + for COLOR in "${COLORS[@]}"; do + output=$(echo test | red --bg ${COLOR} --bold) + [[ "$output" == *"test"* ]] + done +} + + +@test "$(red notred --fg blue) \${s}" { + run notred test + [[ "${lines[0]}" == "test" ]] +} + +@test "$(red red) \${s} | $(red notred --fg blue)" { + output=$(red test | notred) + [[ "$output" == *"test"* ]] +} + +@test "which $(red red)" { + run which red + [ "${status}" -eq 0 ] +} + +@test "which $(red notred --fg blue)" { + run which notred + [ "${status}" -eq 0 ] +} + +@test "which $(red bake_indent --fg cyan)" { + run which bake_indent + [ "${status}" -eq 0 ] +} + +@test "which $(red bake_step --fg cyan)" { + run which bake_step + [ "${status}" -eq 0 ] +} + +@test "$(red bake_step --fg cyan) \${s}" { + run bake_step --no-color 'Step 1' + [[ "${lines[0]}" == " + Step 1: " ]] +} + + +@test "$(red bake_step --fg cyan) --help" { + run bake_step --help + [ "${lines[0]}" = "Usage: bake_step [OPTIONS] [S]" ] +} + +@test "$(red bake_indent --fg cyan) --help" { + run bake_indent --help + [ "${lines[0]}" = "Usage: bake_indent [OPTIONS]" ] +}