Skip to content

Commit 6e6d80e

Browse files
committed
fixes to target specific variables
- add a layer for recipe execution - fix bugs found in new tests - fix export of an undefined variable
1 parent 6506140 commit 6e6d80e

6 files changed

Lines changed: 155 additions & 51 deletions

File tree

pymake/pymake.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -457,13 +457,9 @@ def check_prefixes(s):
457457
symtable.add_automatic("+", " ".join(rule.prereq_list), rule.get_pos())
458458
symtable.add_automatic("<", rule.prereq_list[0] if len(rule.prereq_list) else "", rule.get_pos())
459459

460-
# TODO target specific variables
461-
462460
cmd_s = recipe.eval(symtable)
463461
# print("execute_recipe \"%r\"" % cmd_s)
464462

465-
symtable.pop_layer()
466-
467463
# Defining Multi-Line Variables.
468464
# "However, note that using two separate lines means make will invoke the shell twice, running
469465
# an independent sub-shell for each line. See Section 5.3 [Recipe Execution], page 46."
@@ -529,6 +525,8 @@ def check_prefixes(s):
529525
break
530526
exit_code = 0
531527

528+
symtable.pop_layer()
529+
532530
return exit_status["error"] if exit_code else exit_status["success"]
533531

534532
def execute(makefile, args):
@@ -657,10 +655,18 @@ def execute(makefile, args):
657655
# this warning catches where I fail to find an implicit rule
658656
logger.warning("I didn't find a recipe to build target=\"%s\"", target)
659657

658+
# target specific variables
659+
if rule.assignment_list:
660+
symtable.push_layer()
661+
for asn in rule.assignment_list:
662+
asn.eval(symtable)
663+
660664
for recipe in rule.recipe_list:
661665
exit_code = execute_recipe(rule, recipe, symtable, args)
662666
if exit_code != 0:
663667
break
668+
if rule.assignment_list:
669+
symtable.pop_layer()
664670
if exit_code != 0:
665671
break
666672

pymake/symtable.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,11 @@ def __init__(self, **kwargs):
259259

260260
self.export_default_value = Export.NOEXPORT
261261

262+
# A name can be marked export before it's been defined. For example,
263+
# export FOO
264+
# FOO:=bar
265+
self.exported_names = set()
266+
262267
self.warn_undefined = kwargs.get("warn_undefined_variables", False)
263268

264269
self._init_builtins()
@@ -321,6 +326,9 @@ def push_layer(self):
321326

322327
def pop_layer(self):
323328
# push top, pop top
329+
if len(self.layers)==1:
330+
# don't allow pop of last layer
331+
raise IndexError(1)
324332
self.layers = self.layers[1:]
325333

326334
def find(self, name, layer_idx=None):
@@ -404,7 +412,12 @@ def add(self, name, value, pos=None):
404412
entry = DefaultEntry(name, value, pos)
405413
else:
406414
entry = FileEntry(name, value, pos)
407-
entry.set_export(self.export_default_value)
415+
416+
# check if the name was exported before a variable was defined
417+
if name in self.exported_names:
418+
entry.set_export(Export.EXPLICIT)
419+
else:
420+
entry.set_export(self.export_default_value)
408421

409422
self._add_entry(entry)
410423
else:
@@ -597,8 +610,9 @@ def variables(self, _):
597610

598611
if len(self.layers)==1:
599612
return " ".join(self.layers[0].keys())
600-
raise NotImplementedError("variables")
601-
# return " ".join(self.symbols.keys())
613+
614+
entries = self._get_entries()
615+
return " ".join(entries.keys())
602616

603617
def is_defined(self, name):
604618
# is this varname in our symbol table (or other mechanisms)
@@ -630,11 +644,13 @@ def export(self, name=None):
630644
return
631645

632646
try:
633-
value = self.find(name).set_export(Export.EXPLICIT)
647+
self.find(name).set_export(Export.EXPLICIT)
634648
except KeyError:
635649
# no such entry
636650
pass
637651

652+
self.exported_names.update((name,))
653+
638654
def unexport(self, name=None):
639655
if name is None:
640656
# export everything
@@ -647,6 +663,12 @@ def unexport(self, name=None):
647663
# no such entry
648664
pass
649665

666+
try:
667+
self.exported_names.remove(name)
668+
except KeyError:
669+
# no such entry, no big deal
670+
pass
671+
650672
def undefine(self, name):
651673
# support the undefine directive
652674
if len(self.layers)==1:
@@ -681,14 +703,20 @@ def _unexport_all(self):
681703
self.export_stop()
682704

683705
def get_exports(self):
706+
# Fetch all the exported variables and their values.
707+
# Used when launching shell commands so need to eval() the expressions
708+
# to get their actual value.
709+
684710
logger.debug("get_exports")
685711
assert self.env_recursion >= 0
686712

687-
# cannot eval() during the self.symbols iteration because we could
688-
# store something in the symbol table that will throw a "dictionary
689-
# changed during iteration" error (for example, .SHELLSTATUS is updated
690-
# internally every time a shell is run)
691-
entries = self._get_entries(lambda e:e.export )
713+
# Separate key:value fetch from .eval() because we could store
714+
# something in the symbol table during eval. For example, .SHELLSTATUS
715+
# is updated internally every time a shell is run.
716+
#
717+
# The .eval() during iteration will throw a "dictionary changed during
718+
# iteration" error.
719+
entries = self._get_entries(lambda e:e.export)
692720

693721
exports = { name:entry.eval(self) for name,entry in entries.items() }
694722

tests/target-specific.mk

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ BAZ:=baz
1111
all:CC:=clang
1212
all:FOO!=echo foo
1313
all:CFLAGS+=-g
14-
all:PREREQ:=a.txt
14+
all:export PREREQ:=a.txt
1515

1616
# PREREQ isn't eval'd so 'all' has no prereqs (the var only applies to the
1717
# recipe apparently)
@@ -20,7 +20,7 @@ all: $(PREREQ)
2020
@printenv CC
2121
@printenv FOO
2222
@printenv CFLAGS
23-
@echo PREREQ=$(PREREQ)
23+
@printenv PREREQ
2424
all:BAR=$(BAZ)
2525

2626
BAZ:=zab

tests/test_export.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,4 +295,18 @@ def test_export_commandline_var_explicit():
295295
# use explicity assign instead of recursive assign
296296
run_test(makefile, expect, extra_args=("FOO:=bar",))
297297

298+
def test_export_before_define():
299+
makefile="""
300+
export FOO
301+
FOO:=foo
302+
@:; printenv FOO
303+
"""
304+
run.simple_test(makefile)
305+
306+
def test_unexpert_unknown():
307+
makefile="""
308+
unexport FOO
309+
@:;@:
310+
"""
311+
run.simple_test(makefile)
298312

tests/test_makefile.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ def _run_pymake(infilename):
113113
"ignoreandsilent.mk",
114114
"rule_without_target.mk",
115115
"shellstatus.mk",
116+
"target-specific.mk",
116117

117118
# FIXME fails with GNU Make 4.3
118119
# "env_recursion.mk",

tests/test_symtable.py

Lines changed: 91 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -32,80 +32,75 @@ def test_recursively_expanded():
3232
value = symbol_table.fetch("CFLAGS")
3333
assert value=="-g -Wall", value
3434

35-
@pytest.mark.skip(reason="FIXME push/pop")
36-
def test_simple_push_pop():
35+
def test_simple_layer():
3736
symbol_table = symtable.SymbolTable()
38-
symbol_table.add("target", ["abcdefghijklmnopqrstuvwxyz"])
37+
symbol_table.add("target", "abcdefghijklmnopqrstuvwxyz")
3938

40-
symbol_table.push("target")
41-
symbol_table.add("target", ["12345"])
39+
symbol_table.push_layer()
40+
symbol_table.add("target", "12345")
4241
value = symbol_table.fetch("target")
43-
assert value == ["12345"]
42+
assert value == "12345"
4443

45-
symbol_table.pop("target")
44+
symbol_table.pop_layer()
4645
value = symbol_table.fetch("target")
47-
assert value == ["abcdefghijklmnopqrstuvwxyz"]
46+
assert value == "abcdefghijklmnopqrstuvwxyz"
4847

49-
@pytest.mark.skip(reason="FIXME push/pop")
5048
def test_push_push_pop_pop():
5149
symbol_table = symtable.SymbolTable()
52-
symbol_table.add("target", ["abcdefghijklmnopqrstuvwxyz"])
50+
symbol_table.add("target", "abcdefghijklmnopqrstuvwxyz")
5351

54-
symbol_table.push("target")
55-
symbol_table.add("target", ["12345"])
52+
symbol_table.push_layer()
53+
symbol_table.add("target", "12345")
5654

57-
symbol_table.push("target")
58-
symbol_table.add("target", ["67890"])
55+
symbol_table.push_layer()
56+
symbol_table.add("target", "67890")
5957

6058
value = symbol_table.fetch("target")
61-
assert value == ["67890"]
59+
assert value == "67890"
6260

63-
symbol_table.pop("target")
61+
symbol_table.pop_layer()
6462
value = symbol_table.fetch("target")
65-
assert value == ["12345"]
63+
assert value == "12345"
6664

67-
symbol_table.pop("target")
65+
symbol_table.pop_layer()
6866
value = symbol_table.fetch("target")
69-
assert value == ["abcdefghijklmnopqrstuvwxyz"]
67+
assert value == "abcdefghijklmnopqrstuvwxyz"
7068

71-
@pytest.mark.skip(reason="FIXME push/pop")
7269
def test_push_pop_undefined():
7370
# "If var was undefined before the foreach function call, it is undefined after the call."
7471
symbol_table = symtable.SymbolTable()
7572

76-
symbol_table.push("target")
77-
symbol_table.add("target", ["12345"])
73+
symbol_table.push_layer()
74+
symbol_table.add("target", "12345")
7875
value = symbol_table.fetch("target")
79-
assert value == ["12345"]
76+
assert value == "12345"
8077

81-
symbol_table.pop("target")
78+
symbol_table.pop_layer()
8279
value = symbol_table.fetch("target")
8380
assert value==""
8481

85-
@pytest.mark.skip(reason="FIXME push/pop")
8682
def test_push_pop_pop():
8783
# too many pops
8884
symbol_table = symtable.SymbolTable()
89-
symbol_table.add("target", ["abcdefghijklmnopqrstuvwxyz"])
85+
symbol_table.add("target", "abcdefghijklmnopqrstuvwxyz")
9086

91-
symbol_table.push("target")
92-
symbol_table.add("target", ["12345"])
87+
symbol_table.push_layer()
88+
symbol_table.add("target", "12345")
9389
value = symbol_table.fetch("target")
94-
assert value == ["12345"]
90+
assert value == "12345"
9591

96-
symbol_table.pop("target")
92+
symbol_table.pop_layer()
9793
value = symbol_table.fetch("target")
98-
assert value == ["abcdefghijklmnopqrstuvwxyz"]
94+
assert value == "abcdefghijklmnopqrstuvwxyz"
9995

10096
try:
101-
symbol_table.pop("target")
97+
symbol_table.pop_layer()
10298
except IndexError:
10399
pass
104100
else:
105101
# expected IndexError
106102
assert 0
107103

108-
@pytest.mark.skip(reason="FIXME push/pop")
109104
def test_env_var():
110105
# environment variables should act like regular vars
111106
symbol_table = symtable.SymbolTable()
@@ -115,12 +110,12 @@ def test_env_var():
115110
assert save_path
116111
assert symbol_table.origin("PATH") == "environment"
117112

118-
symbol_table.push("PATH")
113+
symbol_table.push_layer()
119114
symbol_table.add("PATH", "a:b:c:")
120115
value = symbol_table.fetch("PATH")
121116
assert value == "a:b:c:"
122117

123-
symbol_table.pop("PATH")
118+
symbol_table.pop_layer()
124119

125120
path = symbol_table.fetch("PATH")
126121
assert path==save_path
@@ -144,6 +139,13 @@ def test_is_defined():
144139
symbol_table.add("FOO", "BAR")
145140
assert symbol_table.is_defined("FOO")
146141

142+
assert not symbol_table.is_defined("DAVE")
143+
symbol_table.push_layer()
144+
symbol_table.add("DAVE","dave")
145+
assert symbol_table.is_defined("DAVE")
146+
symbol_table.pop_layer()
147+
assert not symbol_table.is_defined("DAVE")
148+
147149
def test_maybe_add():
148150
symbol_table = symtable.SymbolTable()
149151

@@ -286,5 +288,58 @@ def test_command_line_multiple_var():
286288
assert symbol_table.origin("FOO") == "command line"
287289

288290
def test_layers():
289-
pass
291+
symbol_table = symtable.SymbolTable()
292+
symbol_table.add("FOO", Literal("foo"))
293+
symbol_table.push_layer()
294+
value = symbol_table.fetch("FOO")
295+
assert value == "foo" # unchanged
296+
297+
def test_layers_variables():
298+
symbol_table = symtable.SymbolTable()
299+
300+
symbol_table.add("FOO", Literal("foo"))
301+
varlist = symbol_table.variables(None)
302+
assert "FOO" in varlist
303+
304+
symbol_table.push_layer()
305+
varlist = symbol_table.variables(None)
306+
assert "FOO" in varlist
307+
308+
@pytest.mark.skip("undefine FIXME")
309+
def test_layers_undefine():
310+
symbol_table = symtable.SymbolTable()
311+
312+
symbol_table.add("FOO", Literal("foo"))
313+
symbol_table.push_layer()
314+
symbol_table.add("FOO", Literal("bar"))
315+
316+
symbol_table.undefine("FOO")
317+
varlist = symbol_table.variables(None)
318+
assert "FOO" not in varlist
319+
symbol_table.pop_layer()
320+
varlist = symbol_table.variables(None)
321+
assert "FOO" in varlist
322+
323+
@pytest.mark.skip("foo")
324+
def test_layer_append():
325+
symbol_table = symtable.SymbolTable()
326+
327+
symbol_table.add("FOO", Literal("foo"))
328+
symbol_table.push_layer()
329+
symbol_table.append("FOO", Literal("bar"))
330+
value = symbol_table.fetch("FOO")
331+
# assert value=="foobar"
332+
symbol_table.pop_layer()
333+
value = symbol_table.fetch("FOO")
334+
# assert value=="foo"
335+
336+
def test_global_export_layers():
337+
makefile="""
338+
export
339+
340+
all:FOO:=bar
341+
all:
342+
printenv FOO
343+
"""
344+
run.simple_test(makefile)
290345

0 commit comments

Comments
 (0)