* [WIP 0/2] Adding support for shell arithmetics
@ 2013-05-07 9:22 Olof Johansson
2013-05-07 9:22 ` [WIP 1/2] pysh: Say what kind of token isn't implemented Olof Johansson
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Olof Johansson @ 2013-05-07 9:22 UTC (permalink / raw)
To: bitbake-devel
Hi,
A week or so I found out that shell fragments in bitbake recipes
don't support shell arithmetics (`$((1+1))`). The reason for this
is the shell lexing throwing NotImplemented exceptions. I've made
some progress in adding support for shell arithmetics, but I have
a hard time getting support for corner cases.
It was suprising to see that currently, only a subset of posix
shell scripts is supported, even though they are sent through to
be executed by the system shell. I think I understand the reason
for having to do the shell lexing; knowing what functions and
variables to export right? But I wonder if it's possible to do
the shell lexing as a best effort, and ignore any errors.
What are the risks with this approach? Is it feasibile to do so?
This patch series is a work in progress, and does support the use
cases we had issues with, however, I currently skip one unit
test, as I can't get it to work. Comments and feedback much
appreciated!
The branch is also pushed to github at:
https://github.com/olof/bitbake/tree/topic/shlex_shell_arith
Olof Johansson (2):
pysh: Say what kind of token isn't implemented
initial work on supporting shell arith
bin/bitbake-selftest | 1 +
lib/bb/pysh/pyshlex.py | 48 ++++++++++++++++--
lib/bb/tests/codeparser.py | 22 ++++++++
lib/bb/tests/pysh.py | 119 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 186 insertions(+), 4 deletions(-)
create mode 100644 lib/bb/tests/pysh.py
--
1.7.10.4
^ permalink raw reply [flat|nested] 5+ messages in thread* [WIP 1/2] pysh: Say what kind of token isn't implemented 2013-05-07 9:22 [WIP 0/2] Adding support for shell arithmetics Olof Johansson @ 2013-05-07 9:22 ` Olof Johansson 2013-05-07 9:22 ` [WIP 2/2] initial work on supporting shell arith Olof Johansson 2013-06-14 13:55 ` [WIP 0/2] Adding support for shell arithmetics Olof Johansson 2 siblings, 0 replies; 5+ messages in thread From: Olof Johansson @ 2013-05-07 9:22 UTC (permalink / raw) To: bitbake-devel When the shell lexer finds an unrecognized dollar token, the error message should contain what kind of token it is having problems with. Signed-off-by: Olof Johansson <olof.johansson@axis.com> --- lib/bb/pysh/pyshlex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bb/pysh/pyshlex.py b/lib/bb/pysh/pyshlex.py index b977b5e..b301236 100644 --- a/lib/bb/pysh/pyshlex.py +++ b/lib/bb/pysh/pyshlex.py @@ -292,7 +292,7 @@ class WordLexer: elif sep=='${': parsefunc = self._parse_parameter else: - raise NotImplementedError() + raise NotImplementedError(sep) pos, closed = parsefunc(buf, result, eof) return pos, closed -- 1.7.10.4 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* [WIP 2/2] initial work on supporting shell arith 2013-05-07 9:22 [WIP 0/2] Adding support for shell arithmetics Olof Johansson 2013-05-07 9:22 ` [WIP 1/2] pysh: Say what kind of token isn't implemented Olof Johansson @ 2013-05-07 9:22 ` Olof Johansson 2013-06-14 13:55 ` [WIP 0/2] Adding support for shell arithmetics Olof Johansson 2 siblings, 0 replies; 5+ messages in thread From: Olof Johansson @ 2013-05-07 9:22 UTC (permalink / raw) To: bitbake-devel Signed-off-by: Olof Johansson <olof.johansson@axis.com> --- bin/bitbake-selftest | 1 + lib/bb/pysh/pyshlex.py | 46 +++++++++++++++-- lib/bb/tests/codeparser.py | 22 ++++++++ lib/bb/tests/pysh.py | 119 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 lib/bb/tests/pysh.py diff --git a/bin/bitbake-selftest b/bin/bitbake-selftest index 48a58fe..904a801 100755 --- a/bin/bitbake-selftest +++ b/bin/bitbake-selftest @@ -29,6 +29,7 @@ tests = ["bb.tests.codeparser", "bb.tests.cow", "bb.tests.data", "bb.tests.fetch", + "bb.tests.pysh", "bb.tests.utils"] for t in tests: diff --git a/lib/bb/pysh/pyshlex.py b/lib/bb/pysh/pyshlex.py index b301236..0c68e57 100644 --- a/lib/bb/pysh/pyshlex.py +++ b/lib/bb/pysh/pyshlex.py @@ -211,7 +211,45 @@ class WordLexer: else: #Keep everything until the separator and defer processing return pos, False - + + def _find_balanced(self, buf, delim, escapes=True): + expr = 1 + pos = 0 + + while expr > 0 and pos < len(buf): + # open delimiter + if ( delim[0] is not None and + ''.join(buf[pos : pos+len(delim[0])]) == delim[0]): + expr += 1 + pos += len(delim[0]) - 1 + # closing delimiter + elif ''.join(buf[pos : pos+len(delim[1])]) == delim[1]: + expr -= 1 + pos += len(delim[1]) - 1 + elif escapes and buf[pos] == '\\': + pos += 1 # skip next, it's escaped! + + pos += 1 + + if expr > 0: + return -1 # Could not find matching pair of delims + return pos + + def _parse_arithmetic(self, buf, result, eof): + if not buf: + raise NeedMore() + + pos = self._find_balanced(buf, ("$((", "))")) + + if pos < 0: + raise NeedMore() + + result[-1] += ''.join(buf[:pos-2]) + result.append("))") + + has_eof = len(buf) <= pos + return pos, has_eof + def _parse_command(self, buf, result, eof): if not buf: raise NeedMore() @@ -229,7 +267,7 @@ class WordLexer: return pos+1, True else: return pos, False - + def _parse_parameter(self, buf, result, eof): if not buf: raise NeedMore() @@ -289,6 +327,8 @@ class WordLexer: sep = result[0] if sep=='$(': parsefunc = self._parse_command + elif sep=='$((': + parsefunc = self._parse_arithmetic elif sep=='${': parsefunc = self._parse_parameter else: @@ -386,7 +426,7 @@ def make_wordtree(token, here_document=False): try: result, remaining = WordLexer(heredoc = here_document).add(remaining, True) except NeedMore: - raise ShellSyntaxError('Invalid token "%s"') + raise ShellSyntaxError('Invalid token "%s"' % remaining) tree.append(result) diff --git a/lib/bb/tests/codeparser.py b/lib/bb/tests/codeparser.py index 938b04b..debb933 100644 --- a/lib/bb/tests/codeparser.py +++ b/lib/bb/tests/codeparser.py @@ -110,6 +110,28 @@ ${D}${libdir}/pkgconfig/*.pc self.parseExpression("foo=$(echo bar)") self.assertExecs(set(["echo"])) + def test_assign_arith(self): + self.parseExpression("python2.$((8-1))") + self.assertExecs(set(["python2.$((8-1))"])) + + def test_assign_arith_only(self): + self.parseExpression("$((8-1))") + self.assertExecs(set(["$((8-1))"])) + + @unittest.skip( + "FIXME: multiple shell arith expressions are not supported") + def test_assign_arith_dual(self): + self.parseExpression("$((1+1))to$((4-1))") + self.assertExecs(set(["$((1+1))to$((4-1))"])) + + def test_assign_arith_nested1(self): + self.parseExpression("$((1+$((1))))") + self.assertExecs(set(["$((1+$((1))))"])) + + def test_assign_arith_nested2(self): + self.parseExpression("$(( 1 + $(( 1 )) ))") + self.assertExecs(set(["$(( 1 + $(( 1 )) ))"])) + def test_shell_unexpanded(self): self.setEmptyVars(["QT_BASE_NAME"]) self.parseExpression('echo "${QT_BASE_NAME}"') diff --git a/lib/bb/tests/pysh.py b/lib/bb/tests/pysh.py new file mode 100644 index 0000000..3f64f14 --- /dev/null +++ b/lib/bb/tests/pysh.py @@ -0,0 +1,119 @@ +# ex:ts=4:sw=4:sts=4:et +# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- +# +# BitBake Tests for the pysh lexer (pysh/) +# +# Copyright (C) 2013 Olof Johansson <olof.johansson@axis.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +import unittest +from bb.pysh.pyshlex import WordLexer, NeedMore + +orig_init = WordLexer.__init__ + +class WordLexerParseTest(unittest.TestCase): + # mock out WordLexer's constructor + def SetUp(self): + WordLexer.__init__ = lambda: None + + def TearDown(self): + WordLexer.__init__ = orig_init + + # The arithmetic tests make sure that WordLexer's _parse_arithmetic + # method can parse out the expected shell arithmetic expression, + # including support for nested expressions. + def assertArithmeticParser(self, expr, expect=None, eof=True, length=None): + expr = expr[3:] # strip off $(( + result = ["$((", ""] + buf = list(expr) + + # Unless stated otherwise, let's assume the complete buf + # is an shell arithmetic expression. + if not expect: + expect = ["$((", expr[:-2], "))"] + if not length: + length = len(expr) + + wl = WordLexer() + pos, got_eof = WordLexer._parse_arithmetic(wl, buf, result, True) + + self.assertEqual(buf, list(expr)) + self.assertEqual(result, expect) + self.assertEqual(pos, length) + self.assertEqual(got_eof, eof) + + def test_parse_arithmetic_simple(self): + self.assertArithmeticParser("$((1+1))") + + def test_parse_arithmetic_simple_trailing(self): + self.assertArithmeticParser( + "$((1+1)); uptime", + expect=["$((", "1+1", "))"], + length=5, + eof=False) + + def test_parse_arithmetic_incomplete(self): + wl = WordLexer() + res = ["$((", ""] + buf = list("10+") + + needs_more = False + try: + pos, got_eof = WordLexer._parse_arithmetic(wl, buf, res, True) + except NeedMore: + needs_more = True + + self.assertTrue(needs_more) + + def test_parse_arithmetic_empty(self): + wl = WordLexer() + res = ["$((", ""] + buf = list("") + + needs_more = False + try: + pos, got_eof = WordLexer._parse_arithmetic(wl, buf, res, True) + except NeedMore: + needs_more = True + + self.assertTrue(needs_more) + + def test_parse_arithmetic_simple_whitespace(self): + self.assertArithmeticParser("$(( 1 + 1 ))") + + def test_parse_arithmetic_nested1(self): + self.assertArithmeticParser("$((1+$((1+2))))") + + def test_parse_arithmetic_nested2(self): + self.assertArithmeticParser("$(($((1+2))+$((1+2))))") + + def assertCommandParser(self, command="", sep=("$(", ")"), eof=True): + result = [sep[0], ""] + buf = ''.join([command, sep[1]]) + + wl = WordLexer() + pos, got_eof = WordLexer._parse_command(wl, buf, result, True) + + self.assertEqual(result, [sep[0], command, sep[1]]) + + def test_parse_command_simple(self): + self.assertCommandParser("uptime") + + def test_parse_command_backticks(self): + self.assertCommandParser("echo bar", sep=("`", "`")) + + #def test_parse_command_nested(self): + # self.assertCommandParser("echo $(uptime)") -- 1.7.10.4 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [WIP 0/2] Adding support for shell arithmetics 2013-05-07 9:22 [WIP 0/2] Adding support for shell arithmetics Olof Johansson 2013-05-07 9:22 ` [WIP 1/2] pysh: Say what kind of token isn't implemented Olof Johansson 2013-05-07 9:22 ` [WIP 2/2] initial work on supporting shell arith Olof Johansson @ 2013-06-14 13:55 ` Olof Johansson 2013-06-17 16:36 ` Richard Purdie 2 siblings, 1 reply; 5+ messages in thread From: Olof Johansson @ 2013-06-14 13:55 UTC (permalink / raw) To: bitbake-devel@lists.openembedded.org On 2013-05-07 11:22, Olof Johansson wrote: > Hi, > > A week or so I found out that shell fragments in bitbake recipes > don't support shell arithmetics (`$((1+1))`). The reason for this > is the shell lexing throwing NotImplemented exceptions. I've made > some progress in adding support for shell arithmetics, but I have > a hard time getting support for corner cases. > > It was suprising to see that currently, only a subset of posix > shell scripts is supported, even though they are sent through to > be executed by the system shell. I think I understand the reason > for having to do the shell lexing; knowing what functions and > variables to export right? But I wonder if it's possible to do > the shell lexing as a best effort, and ignore any errors. > > What are the risks with this approach? Is it feasibile to do so? > > This patch series is a work in progress, and does support the use > cases we had issues with, however, I currently skip one unit > test, as I can't get it to work. Comments and feedback much > appreciated! Did anybody have any feedback on this? I have had a hard time understanding the design of the lexing. I really could use some pointers --- is there some documents somewhere that could make me understand it better? I haven't had time to work on this lately, but it would be great to get this fixed, so I figured I'll try to take some time in the near future. /Olof PS. Saw that RP merged the exception message patch, thanks :). ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [WIP 0/2] Adding support for shell arithmetics 2013-06-14 13:55 ` [WIP 0/2] Adding support for shell arithmetics Olof Johansson @ 2013-06-17 16:36 ` Richard Purdie 0 siblings, 0 replies; 5+ messages in thread From: Richard Purdie @ 2013-06-17 16:36 UTC (permalink / raw) To: Olof Johansson; +Cc: bitbake-devel@lists.openembedded.org On Fri, 2013-06-14 at 15:55 +0200, Olof Johansson wrote: > On 2013-05-07 11:22, Olof Johansson wrote: > > Hi, > > > > A week or so I found out that shell fragments in bitbake recipes > > don't support shell arithmetics (`$((1+1))`). The reason for this > > is the shell lexing throwing NotImplemented exceptions. I've made > > some progress in adding support for shell arithmetics, but I have > > a hard time getting support for corner cases. > > > > It was suprising to see that currently, only a subset of posix > > shell scripts is supported, even though they are sent through to > > be executed by the system shell. I think I understand the reason > > for having to do the shell lexing; knowing what functions and > > variables to export right? But I wonder if it's possible to do > > the shell lexing as a best effort, and ignore any errors. > > > > What are the risks with this approach? Is it feasibile to do so? > > > > This patch series is a work in progress, and does support the use > > cases we had issues with, however, I currently skip one unit > > test, as I can't get it to work. Comments and feedback much > > appreciated! > > Did anybody have any feedback on this? I have had a hard time > understanding the design of the lexing. I really could use some > pointers --- is there some documents somewhere that could make me > understand it better? I haven't had time to work on this lately, > but it would be great to get this fixed, so I figured I'll try to > take some time in the near future. > > /Olof > > PS. Saw that RP merged the exception message patch, thanks :). That one was easy :) I'm afraid I don't have much knowledge of the sh lexing code, Chris might have more knowledge than me... Cheers, Richard ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2013-06-17 16:36 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2013-05-07 9:22 [WIP 0/2] Adding support for shell arithmetics Olof Johansson 2013-05-07 9:22 ` [WIP 1/2] pysh: Say what kind of token isn't implemented Olof Johansson 2013-05-07 9:22 ` [WIP 2/2] initial work on supporting shell arith Olof Johansson 2013-06-14 13:55 ` [WIP 0/2] Adding support for shell arithmetics Olof Johansson 2013-06-17 16:36 ` Richard Purdie
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.