diff --git a/cibuildwheel/bashlex_eval.py b/cibuildwheel/bashlex_eval.py index b5e478c8..57791b2c 100644 --- a/cibuildwheel/bashlex_eval.py +++ b/cibuildwheel/bashlex_eval.py @@ -51,26 +51,22 @@ def evaluate_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str: def evaluate_word_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str: - word_start = node.pos[0] - word_end = node.pos[1] - word_string = context.input[word_start:word_end] - letters = list(word_string) + value = node.word for part in node.parts: - part_start = part.pos[0] - word_start - part_end = part.pos[1] - word_start + part_string = context.input[part.pos[0]:part.pos[1]] + part_value = evaluate_node(part, context=context) - # Set all the characters in the part to None - for i in range(part_start, part_end): - letters[i] = '' + if part_string not in value: + raise RuntimeError( + 'bash parse failed. part "{}" not found in "{}". Word was "{}". Full input was "{}"'.format( + part_string, value, node.word, context.input, + ) + ) - letters[part_start] = evaluate_node(part, context=context) + value = value.replace(part_string, part_value, 1) - # remove the None letters and concat - value = ''.join(letters) - - # apply bash-like quotes/whitespace treatment - return ' '.join(word.strip() for word in shlex.split(value)) + return value def evaluate_command_node(node: bashlex.ast.node, context: NodeExecutionContext) -> str: diff --git a/test/test_environment.py b/test/test_environment.py index 03bf3bd2..dd2de0ea 100644 --- a/test/test_environment.py +++ b/test/test_environment.py @@ -65,7 +65,8 @@ def test_overridden_path(tmp_path, capfd): (new_path / 'python').touch(mode=0o777) utils.cibuildwheel_run(project_dir, output_dir=output_dir, add_env={ - 'CIBW_ENVIRONMENT': f'''PATH="{new_path}{os.pathsep}$PATH"''', + 'NEW_PATH': str(new_path), + 'CIBW_ENVIRONMENT': f'''PATH="$NEW_PATH{os.pathsep}$PATH"''', }) assert len(os.listdir(output_dir)) == 0 diff --git a/unit_test/environment_test.py b/unit_test/environment_test.py index 4e10e07c..0827cc0d 100644 --- a/unit_test/environment_test.py +++ b/unit_test/environment_test.py @@ -103,3 +103,23 @@ def test_operators_inside_eval(): environment_dict = environment_recipe.as_dictionary(os.environ.copy()) assert environment_dict.get('SOMETHING') == 'a\nb\nc' + + +def test_substitution_with_backslash(): + environment_recipe = parse_environment('PATH2="somewhere_else;$PATH1"') + + # pass the existing process env so PATH is available + environment_dict = environment_recipe.as_dictionary(prev_environment={ + 'PATH1': 'c:\\folder\\' + }) + + assert environment_dict.get('PATH2') == 'somewhere_else;c:\\folder\\' + + +def test_awkwardly_quoted_variable(): + environment_recipe = parse_environment('VAR2=something"like this""$VAR1"$VAR1$(echo "theres more")"$(echo "and more!")"') + + # pass the existing process env so PATH is available + environment_dict = environment_recipe.as_dictionary({'VAR1': 'but wait'}) + + assert environment_dict.get('VAR2') == 'somethinglike thisbut waitbut waittheres moreand more!'