From 45a76a1803ab5c78e95cb623d4d2d28f69dab31b Mon Sep 17 00:00:00 2001 From: ivan kotov <55151952+ivan2201@users.noreply.github.com> Date: Mon, 24 Nov 2025 06:56:44 +0300 Subject: [PATCH] Added the ability to escape the '$' character before '(' as "$$" which will allow using the "$()" and "$(())" expressions of bash, sh, and other shells. (#467) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The '$' character is now escaped as the "$$" sequence when a non-zero-length "$$" character sequence ends with the "$(" sequence or the '(' character. This allows the use of SHELL $(...) or $((...)) expressions within bjam expressions such as "actions { ... }" or "[ SHELL ... ]". --------- Co-authored-by: René Ferdinand Rivera Morell --- src/engine/function.cpp | 14 +++++++ test/escaping_dollar_before_round_bracket.py | 39 ++++++++++++++++++++ test/test_all.py | 1 + 3 files changed, 54 insertions(+) create mode 100755 test/escaping_dollar_before_round_bracket.py diff --git a/src/engine/function.cpp b/src/engine/function.cpp index 3a8e91de6..0e78c979f 100644 --- a/src/engine/function.cpp +++ b/src/engine/function.cpp @@ -2363,6 +2363,20 @@ static int32_t try_parse_variable( char const * * s_, char const * * string, VAR_PARSE_GROUP * out ) { char const * s = *s_; + // escaping '$' with "$$" sequence when it needed (sequence of multiple "$$" before "$(" or '(') + if ( s[ 0 ] == '$' && s[ 1 ] == '$' ) + { + // count repeats of "$$" and checks that sequence used before '(' or "$(" (when escaping needed) + for(s += 2; s[ 0 ] == '$' && s[ 1 ] == '$'; s += 2); + if(s[ 0 ] == '(' || (s[ 0 ] == '$' && s[ 1 ] == '(')) { + // save escaped '$'s as half of found "$$" sequence + size_t repeats = (s - *s_) / 2; + var_parse_group_maybe_add_constant( out, *string, s - repeats ); + *string = s; + } + *s_ = s; + return 1; + } if ( s[ 0 ] == '$' && s[ 1 ] == '(' ) { var_parse_group_maybe_add_constant( out, *string, s ); diff --git a/test/escaping_dollar_before_round_bracket.py b/test/escaping_dollar_before_round_bracket.py new file mode 100755 index 000000000..1ad451246 --- /dev/null +++ b/test/escaping_dollar_before_round_bracket.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +# Copyright Ivan Kotov 2025. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE.txt or copy at +# https://www.bfgroup.xyz/b2/LICENSE.txt) + +# Regression test. When Jamfile contained "using whatever ; " and the 'whatever' +# module declared a project, then all targets in Jamfile were considered to be +# declared in the project associated with 'whatever', not with the Jamfile. + +import BoostBuild + +t = BoostBuild.Tester(use_test_config=False) + +t.write("jamroot.jam", """\ + +rule test { + value = zero ; + echo $(value) ; + echo $$(value) ; + echo $$$(value) ; + echo $$$$(value) ; + echo $$$$ ; +} + +test a.test ; + +""") + +t.run_build_system(stdout="""zero +$(value) +$zero +$$(value) +$$$$ +...found 1 target... +""") + +t.cleanup() diff --git a/test/test_all.py b/test/test_all.py index 8542efacb..65a2aa330 100755 --- a/test/test_all.py +++ b/test/test_all.py @@ -293,6 +293,7 @@ tests = ["abs_workdir", "dll_path", "double_loading", "duplicate", + "escaping_dollar_before_round_bracket", "example_libraries", "example_make", "exit_status",