Skip to content
GitLab
Menu
Why GitLab
Pricing
Contact Sales
Explore
Why GitLab
Pricing
Contact Sales
Explore
Sign in
Get free trial
Primary navigation
Search or go to…
Project
buildstream
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Container registry
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Privacy statement
Keyboard shortcuts
?
What's new
6
Snippets
Groups
Projects
This is an archived project. Repository and other project resources are read-only.
Show more breadcrumbs
BuildStream
buildstream
Commits
a4ae51aa
Commit
a4ae51aa
authored
6 years ago
by
Daniel Silverstone
Browse files
Options
Downloads
Patches
Plain Diff
Foldme!
parent
96f8c019
No related branches found
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
buildstream/_variables.py
+72
-211
72 additions, 211 deletions
buildstream/_variables.py
with
72 additions
and
211 deletions
buildstream/_variables.py
+
72
−
211
View file @
a4ae51aa
...
...
@@ -25,7 +25,7 @@ from . import _yaml
# Variables are allowed to have dashes here
#
_VARIABLE_MATCH
=
r
'
\%\{([a-zA-Z][a-zA-Z0-9_-]*)\}
'
PARSE_EXPANSION
=
re
.
compile
(
r
"
\%\{([a-zA-Z][a-zA-Z0-9_-]*)\}
"
)
# The Variables helper object will resolve the variable references in
...
...
@@ -46,7 +46,7 @@ class Variables():
def
__init__
(
self
,
node
):
self
.
original
=
node
self
.
newexp
=
self
.
_resolve
_exp
(
node
)
self
.
newexp
=
self
.
_resolve
(
node
)
self
.
_sanity_check
()
def
get
(
self
,
var
):
...
...
@@ -69,130 +69,35 @@ class Variables():
# LoadError, if the string contains unresolved variable references.
#
def
subst
(
self
,
string
):
# real = self.real_subst(string)
exp
=
self
.
subst_exp
(
string
)
# if real != exp:
# raise LoadError(LoadErrorReason.UNRESOLVED_VARIABLE, "Oh dear!")
# return real
return
exp
def
real_subst
(
self
,
string
):
substitute
,
unmatched
,
_
=
self
.
_subst
(
string
,
self
.
variables
)
unmatched
=
list
(
set
(
unmatched
))
if
unmatched
:
if
len
(
unmatched
)
==
1
:
message
=
"
Unresolved variable
'
{var}
'"
.
format
(
var
=
unmatched
[
0
])
else
:
message
=
"
Unresolved variables:
"
for
unmatch
in
unmatched
:
if
unmatched
.
index
(
unmatch
)
>
0
:
message
+=
'
,
'
message
+=
unmatch
raise
LoadError
(
LoadErrorReason
.
UNRESOLVED_VARIABLE
,
message
)
return
substitute
def
_subst
(
self
,
string
,
variables
):
def
subst_callback
(
match
):
nonlocal
variables
nonlocal
unmatched
nonlocal
matched
token
=
match
.
group
(
0
)
varname
=
match
.
group
(
1
)
exp
=
_parse_expstr
(
string
)
value
=
_yaml
.
node_get
(
variables
,
str
,
varname
,
default_value
=
None
)
if
value
is
not
None
:
# We have to check if the inner string has variables
# and return unmatches for those
unmatched
+=
re
.
findall
(
_VARIABLE_MATCH
,
value
)
matched
+=
[
varname
]
else
:
# Return unmodified token
unmatched
+=
[
varname
]
value
=
token
try
:
return
_expand_expstr
(
self
.
newexp
,
exp
)
except
KeyError
:
unmatched
=
[]
return
value
for
v
in
exp
[
1
][
1
::
2
]:
if
v
not
in
self
.
newexp
:
unmatched
.
append
(
v
)
matched
=
[]
unmatched
=
[]
replacement
=
re
.
sub
(
_VARIABLE_MATCH
,
subst_callback
,
string
)
if
unmatched
:
if
len
(
unmatched
)
==
1
:
message
=
"
Unresolved variable
'
{var}
'"
.
format
(
var
=
unmatched
[
0
])
else
:
message
=
"
Unresolved variables:
"
for
unmatch
in
unmatched
:
if
unmatched
.
index
(
unmatch
)
>
0
:
message
+=
'
,
'
message
+=
unmatch
return
(
replacement
,
unmatched
,
matched
)
raise
LoadError
(
LoadErrorReason
.
UNRESOLVED_VARIABLE
,
message
)
raise
# Variable resolving code
#
# Here we substitute variables for values (resolve variables) repeatedly
# in a dictionary, each time creating a new dictionary until there is no
# more unresolved variables to resolve, or, until resolving further no
# longer resolves anything, in which case we throw an exception.
# Here we resolve all of our inputs into a dictionary, ready for use
# in subst()
def
_resolve
(
self
,
node
):
variables
=
node
# Special case, if notparallel is specified in the variables for this
# element, then override max-jobs to be 1.
# Initialize it as a string as all variables are processed as strings.
#
if
_yaml
.
node_get
(
variables
,
bool
,
'
notparallel
'
,
default_value
=
False
):
variables
[
'
max-jobs
'
]
=
str
(
1
)
# Resolve the dictionary once, reporting the new dictionary with things
# substituted in it, and reporting unmatched tokens.
#
def
resolve_one
(
variables
):
unmatched
=
[]
resolved
=
{}
for
key
,
value
in
_yaml
.
node_items
(
variables
):
# Ensure stringness of the value before substitution
value
=
_yaml
.
node_get
(
variables
,
str
,
key
)
resolved_var
,
item_unmatched
,
matched
=
self
.
_subst
(
value
,
variables
)
if
_wrap_variable
(
key
)
in
resolved_var
:
referenced_through
=
find_recursive_variable
(
key
,
matched
,
variables
)
raise
LoadError
(
LoadErrorReason
.
RECURSIVE_VARIABLE
,
"
{}:
"
.
format
(
_yaml
.
node_get_provenance
(
variables
,
key
))
+
(
"
Variable
'
{}
'
expands to contain a reference to itself.
"
+
"
Perhaps
'
{}
'
contains
'
{}
"
).
format
(
key
,
referenced_through
,
_wrap_variable
(
key
)))
resolved
[
key
]
=
resolved_var
unmatched
+=
item_unmatched
# Carry over provenance
resolved
[
_yaml
.
PROVENANCE_KEY
]
=
variables
[
_yaml
.
PROVENANCE_KEY
]
return
(
resolved
,
unmatched
)
# Resolve it until it's resolved or broken
#
resolved
=
variables
unmatched
=
[
'
dummy
'
]
last_unmatched
=
[
'
dummy
'
]
while
unmatched
:
resolved
,
unmatched
=
resolve_one
(
resolved
)
# Lists of strings can be compared like this
if
unmatched
==
last_unmatched
:
# We've got the same result twice without matching everything,
# something is undeclared or cyclic, compose a summary.
#
summary
=
''
for
unmatch
in
set
(
unmatched
):
for
var
,
provenance
in
self
.
_find_references
(
unmatch
):
line
=
"
unresolved variable
'
{unmatched}
'
in declaration of
'
{variable}
'
at: {provenance}
\n
"
summary
+=
line
.
format
(
unmatched
=
unmatch
,
variable
=
var
,
provenance
=
provenance
)
raise
LoadError
(
LoadErrorReason
.
UNRESOLVED_VARIABLE
,
"
Failed to resolve one or more variable:
\n
{}
"
.
format
(
summary
))
last_unmatched
=
unmatched
return
resolved
def
_resolve_exp
(
self
,
node
):
# Special case, if notparallel is specified in the variables for this
# element, then override max-jobs to be 1.
# Initialize it as a string as all variables are processed as strings.
...
...
@@ -206,13 +111,17 @@ class Variables():
ret
[
sys
.
intern
(
key
)]
=
_parse_expstr
(
value
)
return
ret
# Sanity checks on the resolved variables dictionary
#
# Here we check for loops and for missing inputs which might cause the
# resolved dictionary of inputs to not be sufficient.
def
_sanity_check
(
self
):
#
Ensure that every variable we could expand here has its targets
resolve
d
#
First the check for anything un
resolv
abl
e
summary
=
[]
wants
=
{}
for
k
,
es
in
self
.
newexp
.
items
():
wants
[
k
]
=
wants
.
get
(
k
,
set
())
for
var
in
_exp_deps
(
es
)
:
for
var
in
es
[
1
][
1
::
2
]
:
wants
[
k
].
add
(
var
)
if
var
not
in
self
.
newexp
:
line
=
"
unresolved variable
'
{unmatched}
'
in declaration of
'
{variable}
'
at: {provenance}
"
...
...
@@ -222,6 +131,7 @@ class Variables():
raise
LoadError
(
LoadErrorReason
.
UNRESOLVED_VARIABLE
,
"
Failed to resolve one or more variable:
\n
{}
\n
"
.
format
(
"
\n
"
.
join
(
summary
)))
# And now the cycle checks
def
cycle_check
(
exp
,
visited
,
cleared
):
for
var
in
_exp_deps
(
exp
):
if
var
in
cleared
:
...
...
@@ -230,116 +140,67 @@ class Variables():
raise
LoadError
(
LoadErrorReason
.
RECURSIVE_VARIABLE
,
"
{}:
"
.
format
(
_yaml
.
node_get_provenance
(
self
.
original
,
var
))
+
(
"
Variable
'
{}
'
expands to contain a reference to itself.
"
+
"
Perhaps
'
{}
'
contains
'
{
}
"
).
format
(
var
,
visited
[
-
1
],
_wrap_variable
(
var
)
))
"
Perhaps
'
{}
'
contains
'
%{{{}}
}
"
).
format
(
var
,
visited
[
-
1
],
var
))
visited
.
append
(
var
)
cycle_check
(
self
.
newexp
[
var
],
visited
,
cleared
)
visited
.
pop
()
cleared
.
add
(
var
)
cleared
=
set
()
for
k
,
es
in
self
.
newexp
.
items
():
for
k
,
es
in
self
.
newexp
.
items
():
if
k
not
in
cleared
:
cycle_check
(
es
,
[
k
],
cleared
)
# resolved():
#
# Perform any substitutions necessary and return a dictionary of all
# inputs fully resolved.
#
# Returns:
# (dict): A dictionary mapping variable name to resolved content
#
def
resolved
(
self
):
# Make a fresh dict of the resolved expansions
ret
=
{}
for
k
,
es
in
self
.
newexp
.
items
():
ret
[
k
]
=
_expand_expstr
(
self
.
newexp
,
es
)
return
ret
def
subst_exp
(
self
,
string
):
exp
=
_parse_expstr
(
string
)
try
:
return
_expand_expstr
(
self
.
newexp
,
exp
)
except
KeyError
:
unmatched
=
[]
for
v
in
_exp_deps
(
exp
):
if
v
not
in
self
.
newexp
:
unmatched
.
append
(
v
)
if
unmatched
:
if
len
(
unmatched
)
==
1
:
message
=
"
Unresolved variable
'
{var}
'"
.
format
(
var
=
unmatched
[
0
])
else
:
message
=
"
Unresolved variables:
"
for
unmatch
in
unmatched
:
if
unmatched
.
index
(
unmatch
)
>
0
:
message
+=
'
,
'
message
+=
unmatch
raise
LoadError
(
LoadErrorReason
.
UNRESOLVED_VARIABLE
,
message
)
raise
# Helper function to fetch information about the node referring to a variable
#
def
_find_references
(
self
,
varname
):
fullname
=
_wrap_variable
(
varname
)
for
key
,
value
in
_yaml
.
node_items
(
self
.
original
):
if
fullname
in
value
:
provenance
=
_yaml
.
node_get_provenance
(
self
.
original
,
key
)
yield
(
key
,
provenance
)
def
find_recursive_variable
(
variable
,
matched_variables
,
all_vars
):
matched_values
=
(
_yaml
.
node_get
(
all_vars
,
str
,
key
)
for
key
in
matched_variables
)
for
key
,
value
in
zip
(
matched_variables
,
matched_values
):
if
_wrap_variable
(
variable
)
in
value
:
return
key
# We failed to find a recursive variable
return
None
def
_wrap_variable
(
var
):
return
"
%{
"
+
var
+
"
}
"
## Stuff for new style expansion strings
PARSE_EXPANSION
=
re
.
compile
(
r
"
\%\{([a-zA-Z][a-zA-Z0-9_-]*)\}
"
)
def
__parse_expstr
(
s
):
spl
=
PARSE_EXPANSION
.
split
(
s
)
if
spl
[
-
1
]
==
''
:
spl
=
spl
[:
-
1
]
return
(
len
(
spl
),
[
sys
.
intern
(
s
)
for
s
in
spl
])
def
_exp_deps
(
es
):
for
i
,
s
in
enumerate
(
es
[
1
]):
if
i
%
2
==
1
:
yield
s
# Cache for the parsed expansion strings. While this is nominally
# something which might "waste" memory, in reality each of these
# will live as long as the element which uses it, which is the
# vast majority of the memory usage across the execution of BuildStream.
PARSE_CACHE
=
{}
def
_parse_expstr
(
s
):
# Helper to parse a string into an expansion string tuple, caching
# the results so that future parse requests don't need to think about
# the string
def
_parse_expstr
(
instr
):
try
:
return
PARSE_CACHE
[
s
]
return
PARSE_CACHE
[
instr
]
except
KeyError
:
PARSE_CACHE
[
s
]
=
__parse_expstr
(
s
)
return
PARSE_CACHE
[
s
]
spl
=
PARSE_EXPANSION
.
split
(
instr
)
if
spl
[
-
1
]
==
''
:
spl
=
spl
[:
-
1
]
PARSE_CACHE
[
instr
]
=
(
len
(
spl
),
[
sys
.
intern
(
s
)
for
s
in
spl
])
return
PARSE_CACHE
[
instr
]
#def _expand_expstr(content, value):
# ret = []
# expstack = [(value,0)]
# while expstack:
# ((elen,expanded), idx) = expstack.pop()
# ret.append(expanded[idx])
# idx += 1
# if idx < elen:
# if elen > idx+1:
# expstack.append(((elen,expanded), idx+1))
# expstack.append((content[expanded[idx]], 0))
# return "".join(ret)
def
__expand
(
content
,
value
):
(
elen
,
bits
)
=
value
idx
=
0
while
idx
<
elen
:
yield
bits
[
idx
]
idx
+=
1
if
idx
<
elen
:
yield
from
__expand
(
content
,
content
[
bits
[
idx
]])
idx
+=
1
def
_expand_expstr
(
content
,
value
):
return
""
.
join
(
__expand
(
content
,
value
))
# Helper to expand a given top level expansion string tuple in the context
# of the given dictionary of expansion strings.
#
# Note: Will raise KeyError if any expansion is missing
def
_expand_expstr
(
content
,
topvalue
):
def
__expand
(
value
):
(
elen
,
bits
)
=
value
idx
=
0
while
idx
<
elen
:
yield
bits
[
idx
]
idx
+=
1
if
idx
<
elen
:
yield
from
__expand
(
content
[
bits
[
idx
]])
idx
+=
1
return
""
.
join
(
__expand
(
topvalue
))
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment