pmb/parse/_apkbuild: fix parsing commented lines (!1837)

Properly ignore comments at the end of lines, instead of assuming that
all lines below belong to the attribute:
	subpackages="$pkgname-dev" # $pkgname-lang

Fixes build.postmarketos.org#61, where pmbootstrap would assume that a
random package provides "make", just because the word "make" is written
somewhere below subpackages=" in the APKBUILD and it is parsed
incorrectly.

While at it, also support the ' character for quotations and detect if
a quotation for a value was started, but there is no end quotation sign
in the rest of the file.

I've added tests, and manually checked that this did not introduce any
parsing bugs for all the APKBUILDs in pmaports.git, by running
'pmbootstrap apkbuild_parse' with the old and new code, and diffing the
result.
This commit is contained in:
Oliver Smith 2019-11-30 11:24:35 +01:00
parent 3587812539
commit 5438085e62
No known key found for this signature in database
GPG Key ID: 5AE7F5513E0885CB
2 changed files with 105 additions and 20 deletions

View File

@ -122,6 +122,64 @@ def read_file(path):
return lines
def parse_attribute(attribute, lines, i, path):
"""
Parse one attribute from the APKBUILD.
It may be written across multiple lines, use a quoting sign and/or have
a comment at the end. Some examples:
pkgrel=3
options="!check" # ignore this comment
arch='all !armhf'
depends="
first-pkg
second-pkg"
:param attribute: from the APKBUILD, i.e. "pkgname"
:param lines: \n-terminated list of lines from the APKBUILD
:param i: index of the line we are currently looking at
:param path: full path to the APKBUILD (for error message)
:returns: (found, value, i)
found: True if the attribute was found in line i, False otherwise
value: that was parsed from the line
i: line that was parsed last
"""
# Check for and cut off "attribute="
if not lines[i].startswith(attribute + "="):
return (False, None, i)
value = lines[i][len(attribute + "="):-1]
# Determine end quote sign
end_char = None
for char in ["'", "\""]:
if value.startswith(char):
end_char = char
value = value[1:]
break
# Single line
if not end_char:
return (True, value, i)
if end_char in value:
value = value.split(end_char, 1)[0]
return (True, value, i)
# Parse lines until reaching end quote
i += 1
while i < len(lines):
line = lines[i]
value += " "
if end_char in line:
value += line.split(end_char, 1)[0].strip()
return (True, value.strip(), i)
value += line.strip()
i += 1
raise RuntimeError("Can't find closing quote sign (" + end_char + ") for"
" attribute '" + attribute + "' in: " + path)
def apkbuild(args, path, check_pkgver=True, check_pkgname=True):
"""
Parse relevant information out of the APKBUILD file. This is not meant
@ -148,28 +206,10 @@ def apkbuild(args, path, check_pkgver=True, check_pkgname=True):
ret = {}
for i in range(len(lines)):
for attribute, options in pmb.config.apkbuild_attributes.items():
if not lines[i].startswith(attribute + "="):
found, value, i = parse_attribute(attribute, lines, i, path)
if not found:
continue
# Extend the line value until we reach the ending quote sign
line_value = lines[i][len(attribute + "="):-1]
end_char = None
if line_value.startswith("\""):
end_char = "\""
value = ""
first_line = i
while i < len(lines) - 1:
value += line_value.replace("\"", "").strip()
if not end_char:
break
elif line_value.endswith(end_char):
# This check is needed to allow line break directly after opening quote
if i != first_line or line_value.count(end_char) > 1:
break
value += " "
i += 1
line_value = lines[i][:-1]
# Support depends="$depends hello-world" (#1800)
if attribute == "depends" and ("${depends}" in value or
"$depends" in value):

View File

@ -78,3 +78,48 @@ def test_depends_in_depends(args):
path = pmb_src + "/test/testdata/apkbuild/APKBUILD.depends-in-depends"
apkbuild = pmb.parse.apkbuild(args, path, check_pkgname=False)
assert apkbuild["depends"] == ["first", "second", "third"]
def test_parse_attributes():
# Convenience function for calling the function with a block of text
def func(attribute, block):
lines = block.split("\n")
for i in range(0, len(lines)):
lines[i] += "\n"
i = 0
path = "(testcase in " + __file__ + ")"
print("=== parsing attribute '" + attribute + "' in test block:")
print(block)
print("===")
return pmb.parse._apkbuild.parse_attribute(attribute, lines, i, path)
assert func("depends", "pkgname='test'") == (False, None, 0)
assert func("pkgname", 'pkgname="test"') == (True, "test", 0)
assert func("pkgname", "pkgname='test'") == (True, "test", 0)
assert func("pkgname", "pkgname=test") == (True, "test", 0)
assert func("pkgname", 'pkgname="test\n"') == (True, "test", 1)
assert func("pkgname", 'pkgname="\ntest\n"') == (True, "test", 2)
assert func("pkgname", 'pkgname="test" # random comment\npkgrel=3') == \
(True, "test", 0)
assert func("depends", "depends='\nfirst\nsecond\nthird\n'#") == \
(True, "first second third", 4)
assert func("depends", 'depends="\nfirst\n\tsecond third"') == \
(True, "first second third", 2)
assert func("depends", 'depends=') == (True, "", 0)
with pytest.raises(RuntimeError) as e:
func("depends", 'depends="\nmissing\nend\nquote\sign')
assert str(e.value).startswith("Can't find closing")
with pytest.raises(RuntimeError) as e:
func("depends", 'depends="')
assert str(e.value).startswith("Can't find closing")