Skip to content

Commit 906aa4c

Browse files
authored
Merge pull request mesonbuild#5436 from dcbaker/cmake-lexer-spaces-in-args
Cmake lexer spaces in args
2 parents 94c6da3 + 31ab962 commit 906aa4c

File tree

4 files changed

+158
-34
lines changed

4 files changed

+158
-34
lines changed

mesonbuild/dependencies/base.py

Lines changed: 71 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,13 +1546,27 @@ def _var_to_bool(self, var):
15461546
return True
15471547
return False
15481548

1549-
def _cmake_set(self, tline: CMakeTraceLine):
1549+
def _cmake_set(self, tline: CMakeTraceLine) -> None:
1550+
"""Handler for the CMake set() function in all variaties.
1551+
1552+
comes in three flavors:
1553+
set(<var> <value> [PARENT_SCOPE])
1554+
set(<var> <value> CACHE <type> <docstring> [FORCE])
1555+
set(ENV{<var>} <value>)
1556+
1557+
We don't support the ENV variant, and any uses of it will be ignored
1558+
silently. the other two variates are supported, with some caveats:
1559+
- we don't properly handle scoping, so calls to set() inside a
1560+
function without PARENT_SCOPE set could incorrectly shadow the
1561+
outer scope.
1562+
- We don't honor the type of CACHE arguments
1563+
"""
15501564
# DOC: https://cmake.org/cmake/help/latest/command/set.html
15511565

15521566
# 1st remove PARENT_SCOPE and CACHE from args
15531567
args = []
15541568
for i in tline.args:
1555-
if i == 'PARENT_SCOPE' or len(i) == 0:
1569+
if not i or i == 'PARENT_SCOPE':
15561570
continue
15571571

15581572
# Discard everything after the CACHE keyword
@@ -1564,13 +1578,19 @@ def _cmake_set(self, tline: CMakeTraceLine):
15641578
if len(args) < 1:
15651579
raise self._gen_exception('CMake: set() requires at least one argument\n{}'.format(tline))
15661580

1567-
if len(args) == 1:
1581+
# Now that we've removed extra arguments all that should be left is the
1582+
# variable identifier and the value, join the value back together to
1583+
# ensure spaces in the value are correctly handled. This assumes that
1584+
# variable names don't have spaces. Please don't do that...
1585+
identifier = args.pop(0)
1586+
value = ' '.join(args)
1587+
1588+
if not value:
15681589
# Same as unset
1569-
if args[0] in self.vars:
1570-
del self.vars[args[0]]
1590+
if identifier in self.vars:
1591+
del self.vars[identifier]
15711592
else:
1572-
values = list(itertools.chain(*map(lambda x: x.split(';'), args[1:])))
1573-
self.vars[args[0]] = values
1593+
self.vars[identifier] = value.split(';')
15741594

15751595
def _cmake_unset(self, tline: CMakeTraceLine):
15761596
# DOC: https://cmake.org/cmake/help/latest/command/unset.html
@@ -1619,7 +1639,7 @@ def _cmake_add_custom_target(self, tline: CMakeTraceLine):
16191639

16201640
self.targets[tline.args[0]] = CMakeTarget(tline.args[0], 'CUSTOM', {})
16211641

1622-
def _cmake_set_property(self, tline: CMakeTraceLine):
1642+
def _cmake_set_property(self, tline: CMakeTraceLine) -> None:
16231643
# DOC: https://cmake.org/cmake/help/latest/command/set_property.html
16241644
args = list(tline.args)
16251645

@@ -1629,8 +1649,10 @@ def _cmake_set_property(self, tline: CMakeTraceLine):
16291649

16301650
append = False
16311651
targets = []
1632-
while len(args) > 0:
1652+
while args:
16331653
curr = args.pop(0)
1654+
# XXX: APPEND_STRING is specifically *not* supposed to create a
1655+
# list, is treating them as aliases really okay?
16341656
if curr == 'APPEND' or curr == 'APPEND_STRING':
16351657
append = True
16361658
continue
@@ -1640,60 +1662,75 @@ def _cmake_set_property(self, tline: CMakeTraceLine):
16401662

16411663
targets.append(curr)
16421664

1665+
if not args:
1666+
raise self._gen_exception('CMake: set_property() faild to parse argument list\n{}'.format(tline))
1667+
16431668
if len(args) == 1:
16441669
# Tries to set property to nothing so nothing has to be done
16451670
return
16461671

1647-
if len(args) < 2:
1648-
raise self._gen_exception('CMake: set_property() faild to parse argument list\n{}'.format(tline))
1649-
1650-
propName = args[0]
1651-
propVal = list(itertools.chain(*map(lambda x: x.split(';'), args[1:])))
1652-
propVal = list(filter(lambda x: len(x) > 0, propVal))
1653-
1654-
if len(propVal) == 0:
1672+
identifier = args.pop(0)
1673+
value = ' '.join(args).split(';')
1674+
if not value:
16551675
return
16561676

16571677
for i in targets:
16581678
if i not in self.targets:
16591679
raise self._gen_exception('CMake: set_property() TARGET {} not found\n{}'.format(i, tline))
16601680

1661-
if propName not in self.targets[i].properies:
1662-
self.targets[i].properies[propName] = []
1681+
if identifier not in self.targets[i].properies:
1682+
self.targets[i].properies[identifier] = []
16631683

16641684
if append:
1665-
self.targets[i].properies[propName] += propVal
1685+
self.targets[i].properies[identifier] += value
16661686
else:
1667-
self.targets[i].properies[propName] = propVal
1687+
self.targets[i].properies[identifier] = value
16681688

1669-
def _cmake_set_target_properties(self, tline: CMakeTraceLine):
1689+
def _cmake_set_target_properties(self, tline: CMakeTraceLine) -> None:
16701690
# DOC: https://cmake.org/cmake/help/latest/command/set_target_properties.html
16711691
args = list(tline.args)
16721692

16731693
targets = []
1674-
while len(args) > 0:
1694+
while args:
16751695
curr = args.pop(0)
16761696
if curr == 'PROPERTIES':
16771697
break
16781698

16791699
targets.append(curr)
16801700

1681-
if (len(args) % 2) != 0:
1682-
raise self._gen_exception('CMake: set_target_properties() uneven number of property arguments\n{}'.format(tline))
1683-
1684-
while len(args) > 0:
1685-
propName = args.pop(0)
1686-
propVal = args.pop(0).split(';')
1687-
propVal = list(filter(lambda x: len(x) > 0, propVal))
1688-
1689-
if len(propVal) == 0:
1690-
continue
1701+
# Now we need to try to reconsitute the original quoted format of the
1702+
# arguments, as a property value could have spaces in it. Unlike
1703+
# set_property() this is not context free. There are two approaches I
1704+
# can think of, both have drawbacks:
1705+
#
1706+
# 1. Assume that the property will be capitalized, this is convention
1707+
# but cmake doesn't require it.
1708+
# 2. Maintain a copy of the list here: https://cmake.org/cmake/help/latest/manual/cmake-properties.7.html#target-properties
1709+
#
1710+
# Neither of these is awesome for obvious reasons. I'm going to try
1711+
# option 1 first and fall back to 2, as 1 requires less code and less
1712+
# synchroniztion for cmake changes.
1713+
1714+
arglist = [] # type: typing.List[typing.Tuple[str, typing.List[str]]]
1715+
name = args.pop(0)
1716+
values = []
1717+
for a in args:
1718+
if a.isupper():
1719+
if values:
1720+
arglist.append((name, ' '.join(values).split(';')))
1721+
name = a
1722+
values = []
1723+
else:
1724+
values.append(a)
1725+
if values:
1726+
arglist.append((name, ' '.join(values).split(';')))
16911727

1728+
for name, value in arglist:
16921729
for i in targets:
16931730
if i not in self.targets:
16941731
raise self._gen_exception('CMake: set_target_properties() TARGET {} not found\n{}'.format(i, tline))
16951732

1696-
self.targets[i].properies[propName] = propVal
1733+
self.targets[i].properies[name] = value
16971734

16981735
def _lex_trace(self, trace):
16991736
# The trace format is: '<file>(<line>): <func>(<args -- can contain \n> )\n'

run_unittests.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3681,6 +3681,11 @@ def test_cmake_prefix_path(self):
36813681
testdir = os.path.join(self.unit_test_dir, '60 cmake_prefix_path')
36823682
self.init(testdir, extra_args=['-Dcmake_prefix_path=' + os.path.join(testdir, 'prefix')])
36833683

3684+
@skip_if_no_cmake
3685+
def test_cmake_parser(self):
3686+
testdir = os.path.join(self.unit_test_dir, '61 cmake parser')
3687+
self.init(testdir, extra_args=['-Dcmake_prefix_path=' + os.path.join(testdir, 'prefix')])
3688+
36843689
class FailureTests(BasePlatformTests):
36853690
'''
36863691
Tests that test failure conditions. Build files here should be dynamically
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
project('61 cmake parser')
2+
3+
dep = dependency('mesontest')
4+
5+
# Test a bunch of variations of the set() command
6+
assert(dep.get_variable(cmake : 'VAR_WITHOUT_SPACES') == 'NoSpaces', 'set() without spaces incorrect')
7+
assert(dep.get_variable(cmake : 'VAR_WITH_SPACES') == 'With Spaces', 'set() with spaces incorrect')
8+
9+
assert(dep.get_variable(cmake : 'VAR_WITHOUT_SPACES_PS') == 'NoSpaces', 'set(PARENT_SCOPE) without spaces incorrect')
10+
assert(dep.get_variable(cmake : 'VAR_WITH_SPACES_PS') == 'With Spaces', 'set(PARENT_SCOPE) with spaces incorrect')
11+
12+
assert(dep.get_variable(cmake : 'VAR_THAT_IS_UNSET', default_value : 'sentinal') == 'sentinal', 'set() to unset is incorrect')
13+
assert(dep.get_variable(cmake : 'CACHED_STRING_NS') == 'foo', 'set(CACHED) without spaces is incorrect')
14+
assert(dep.get_variable(cmake : 'CACHED_STRING_WS') == 'foo bar', 'set(CACHED STRING) with spaces is incorrect')
15+
assert(dep.get_variable(cmake : 'CACHED_STRING_ARRAY_NS') == ['foo', 'bar'], 'set(CACHED STRING) without spaces is incorrect')
16+
assert(dep.get_variable(cmake : 'CACHED_STRING_ARRAY_WS') == ['foo', 'foo bar', 'bar'], 'set(CACHED STRING[]) with spaces is incorrect')
17+
18+
# We don't suppor this, so it should be unset.
19+
assert(dep.get_variable(cmake : 'ENV{var}', default_value : 'sentinal') == 'sentinal', 'set(ENV) should be ignored')
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# This should be enough to satisfy the basic parser
2+
set(MESONTEST_VERSION "1.2.3")
3+
set(MESONTEST_LIBRARIES "foo.so")
4+
set(MESONTEST_INCLUDE_DIR "")
5+
set(MESONTEST_FOUND "TRUE")
6+
7+
## Tests for set() in its various forms
8+
9+
# Basic usage
10+
set(VAR_WITHOUT_SPACES "NoSpaces")
11+
set(VAR_WITH_SPACES "With Spaces")
12+
13+
# test of PARENT_SCOPE, requires a function to have a parent scope obviously...
14+
function(foo)
15+
set(VAR_WITHOUT_SPACES_PS "NoSpaces" PARENT_SCOPE)
16+
set(VAR_WITH_SPACES_PS "With Spaces" PARENT_SCOPE)
17+
endfunction(foo)
18+
foo()
19+
20+
# Using set() to unset values
21+
set(VAR_THAT_IS_UNSET "foo")
22+
set(VAR_THAT_IS_UNSET)
23+
24+
# The more advanced form that uses CACHE
25+
# XXX: Why don't we read the type and use that instead of always treat things as strings?
26+
set(CACHED_STRING_NS "foo" CACHE STRING "docstring")
27+
set(CACHED_STRING_WS "foo bar" CACHE STRING "docstring")
28+
set(CACHED_STRING_ARRAY_NS "foo;bar" CACHE STRING "doc string")
29+
set(CACHED_STRING_ARRAY_WS "foo;foo bar;bar" CACHE STRING "stuff" FORCE)
30+
31+
set(CACHED_BOOL ON CACHE BOOL "docstring")
32+
33+
set(CACHED_PATH_NS "foo/bar" CACHE PATH "docstring")
34+
set(CACHED_PATH_WS "foo bar/fin" CACHE PATH "docstring")
35+
36+
set(CACHED_FILEPATH_NS "foo/bar.txt" CACHE FILEPATH "docstring")
37+
set(CACHED_FILEPATH_WS "foo bar/fin.txt" CACHE FILEPATH "docstring")
38+
39+
# Set ENV, we don't support this so it shouldn't be showing up
40+
set(ENV{var}, "foo")
41+
42+
43+
## Tests for set_properties()
44+
# We need something to attach properties too
45+
add_custom_target(MESONTEST_FOO ALL)
46+
47+
set_property(TARGET MESONTEST_FOO PROPERTY FOLDER "value")
48+
set_property(TARGET MESONTEST_FOO APPEND PROPERTY FOLDER "name")
49+
set_property(TARGET MESONTEST_FOO PROPERTY FOLDER "value")
50+
set_property(TARGET MESONTEST_FOO APPEND_STRING PROPERTY FOLDER "name")
51+
52+
set_property(TARGET MESONTEST_FOO PROPERTY FOLDER "value space")
53+
set_property(TARGET MESONTEST_FOO PROPERTY FOLDER "value space")
54+
set_property(TARGET MESONTEST_FOO APPEND PROPERTY FOLDER "name space")
55+
set_property(TARGET MESONTEST_FOO PROPERTY FOLDER "value space")
56+
set_property(TARGET MESONTEST_FOO APPEND_STRING PROPERTY FOLDER "name space")
57+
58+
## Tests for set_target_properties()
59+
set_target_properties(MESONTEST_FOO PROPERTIES FOLDER "value")
60+
set_target_properties(MESONTEST_FOO PROPERTIES FOLDER "value space")
61+
set_target_properties(MESONTEST_FOO PROPERTIES FOLDER "value" OUTPUT_NAME "another value")
62+
set_target_properties(MESONTEST_FOO PROPERTIES FOLDER "value space" OUTPUT_NAME "another value")
63+
set_target_properties(MESONTEST_FOO PROPERTIES FOLDER "value space" OUTPUT_NAME "value")

0 commit comments

Comments
 (0)