From 02e53d9ba6a1fce83314599facd383776d11b0a3 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 15 Dec 2023 17:15:24 -0700 Subject: [PATCH] Fix tests with bash 5.2.21 --- toltec/bash.py | 64 +++++++++++++++++++++-------------- toltec/recipe_parsers/bash.py | 27 +++++---------- 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/toltec/bash.py b/toltec/bash.py index 1f6f094..b81d3aa 100644 --- a/toltec/bash.py +++ b/toltec/bash.py @@ -26,9 +26,8 @@ class ScriptError(Exception): # from the result of `get_declarations()`. Subset of the list at: # default_variables = { + "_", "BASH", - "BASHOPTS", - "BASHPID", "BASH_ALIASES", "BASH_ARGC", "BASH_ARGV", @@ -36,10 +35,13 @@ class ScriptError(Exception): "BASH_CMDS", "BASH_COMMAND", "BASH_LINENO", + "BASH_LOADABLES_PATH", "BASH_SOURCE", "BASH_SUBSHELL", "BASH_VERSINFO", "BASH_VERSION", + "BASHOPTS", + "BASHPID", "COLUMNS", "COMP_WORDBREAKS", "DIRSTACK", @@ -78,10 +80,38 @@ class ScriptError(Exception): "SRANDOM", "TERM", "UID", - "_", } +def _get_bash_stdout(src: str) -> str: + """ + Get the stdout from a bash script + + :param src: bash script to run + :returns: the stdout of the script + """ + env: Dict[str, str] = { + "PATH": os.environ["PATH"], + } + + subshell = subprocess.run( # pylint:disable=subprocess-run-check + ["/usr/bin/env", "bash"], + input=src.encode(), + capture_output=True, + env=env, + ) + + errors = subshell.stderr.decode() + + if subshell.returncode == 2 or "syntax error" in errors: + raise ScriptError(f"Bash syntax error\n{errors}") + + if subshell.returncode != 0 or errors: + raise ScriptError(f"Bash error\n{errors}") + + return subshell.stdout.decode() + + def get_declarations(src: str) -> Tuple[Variables, Functions]: """ Extract all variables and functions defined by a Bash script. @@ -97,28 +127,7 @@ def get_declarations(src: str) -> Tuple[Variables, Functions]: declare -f declare -p """ - env: Dict[str, str] = { - "PATH": os.environ["PATH"], - } - - declarations_subshell = ( - subprocess.run( # pylint:disable=subprocess-run-check - ["/usr/bin/env", "bash"], - input=src.encode(), - capture_output=True, - env=env, - ) - ) - - errors = declarations_subshell.stderr.decode() - - if declarations_subshell.returncode == 2 or "syntax error" in errors: - raise ScriptError(f"Bash syntax error\n{errors}") - - if declarations_subshell.returncode != 0 or errors: - raise ScriptError(f"Bash error\n{errors}") - - declarations = declarations_subshell.stdout.decode() + declarations = _get_bash_stdout(src) # Parse `declare` statements and function statements lexer = shlex.shlex(declarations, posix=True) @@ -319,7 +328,10 @@ def _parse_var(lexer: shlex.shlex) -> Tuple[str, Optional[Any]]: else: string_token = lexer.get_token() or "" if string_token == "$": - string_token = lexer.get_token() or "" + quoted_string = lexer.get_token() or "" + string_token = _get_bash_stdout( + "echo -n $" + shlex.quote(quoted_string) + ) var_value = _parse_string(string_token) else: lexer.push_token(lookahead) diff --git a/toltec/recipe_parsers/bash.py b/toltec/recipe_parsers/bash.py index 0e775b8..b8261ea 100644 --- a/toltec/recipe_parsers/bash.py +++ b/toltec/recipe_parsers/bash.py @@ -37,9 +37,7 @@ def parse(path: str) -> RecipeBundle: definition = recipe.read() variables, functions = bash.get_declarations(definition) - for arch, variables, functions in _instantiate_arch( - path, variables, functions - ): + for arch, variables, functions in _instantiate_arch(path, variables, functions): result[arch] = _parse_recipe(path, variables, functions) return result @@ -174,21 +172,15 @@ def _parse_recipe( # pylint: disable=too-many-locals, disable=too-many-statemen makedepends_raw = _pop_field_indexed(path, variables, "makedepends", []) raw_vars["makedepends"] = makedepends_raw - attrs["makedepends"] = { - Dependency.parse(dep or "") for dep in makedepends_raw - } + attrs["makedepends"] = {Dependency.parse(dep or "") for dep in makedepends_raw} attrs["maintainer"] = raw_vars["maintainer"] = _pop_field_string( path, variables, "maintainer" ) - attrs["image"] = raw_vars["image"] = _pop_field_string( - path, variables, "image", "" - ) + attrs["image"] = raw_vars["image"] = _pop_field_string(path, variables, "image", "") - attrs["arch"] = raw_vars["arch"] = _pop_field_string( - path, variables, "arch" - ) + attrs["arch"] = raw_vars["arch"] = _pop_field_string(path, variables, "arch") if attrs["image"] and "build" not in functions: raise RecipeError( @@ -305,9 +297,7 @@ def _parse_package( # pylint: disable=too-many-locals, disable=too-many-stateme parent.path, variables, "pkgdesc" ) - attrs["url"] = raw_vars["url"] = _pop_field_string( - parent.path, variables, "url" - ) + attrs["url"] = raw_vars["url"] = _pop_field_string(parent.path, variables, "url") attrs["section"] = raw_vars["section"] = _pop_field_string( parent.path, variables, "section" @@ -407,8 +397,7 @@ def _pop_field_string( if not isinstance(value, str): raise RecipeError( path, - f"Field '{name}' must be a string, \ -got a {type(value).__name__}", + f"Field '{name}' must be a string, got a {type(value).__name__}", ) return value @@ -428,10 +417,10 @@ def _pop_field_indexed( value = variables.pop(name) if not isinstance(value, list): + _name = type(value).__name__ raise RecipeError( path, - f"Field '{name}' must be an indexed array, \ -got a {type(value).__name__}", + f"Field '{name}' must be an indexed array, got a {_name}", ) return value