Glob¶
StepUp’s glob() function looks like
static() with a pattern, but does not declare its matches static.
glob() is a pure query.
It scans the file system, records the pattern with the calling step,
and returns the matches.
It creates no node in the workflow and owns nothing it matches.
However, every match must be declared static elsewhere, either by some static() call,
or covered by a static tree.
Example¶
Example source files: docs/getting_started/glob/
Create a subdirectory src/ with three files:
src/intro.txt, src/method_notes.txt and src/result_notes.txt.
Also, create a plan.py file with the following contents:
#!/usr/bin/env python3
from stepup.core.api import copy, glob, run, shq, static
# Declare the sources once. The queries below only look at them.
static("src/")
# A query over all source files.
for path_src in glob("src/*.txt"):
copy(path_src, "dst/")
# An overlapping query, collecting only the notes.
paths_notes = glob("src/*_notes.txt").files()
run(f"cat {shq(paths_notes)} > notes.txt", shell=True, inp=paths_notes, out="notes.txt")
Make the plan executable and run it non-interactively:
This should produce the following output:
DIRECTOR │ Listening on /tmp/stepup-########/director (StepUp Core 4.0.0)
STARTUP │ (Re)initialized boot script
PHASE │ build
START │ ./plan.py
SUCCESS │ ./plan.py
START │ cp -p src/intro.txt dst/intro.txt
SUCCESS │ cp -p src/intro.txt dst/intro.txt
START │ cp -p src/method_notes.txt dst/method_notes.txt
SUCCESS │ cp -p src/method_notes.txt dst/method_notes.txt
START │ cp -p src/result_notes.txt dst/result_notes.txt
SUCCESS │ cp -p src/result_notes.txt dst/result_notes.txt
START │ cat src/method_notes.txt src/result_notes.txt > notes.txt
SUCCESS │ cat src/method_notes.txt src/result_notes.txt > notes.txt
DIRECTOR │ Ran 5 job(s).
DIRECTOR │ Trying to remove 0 deletable file(s) and empty director(y|ies)
DIRECTOR │ See you!
Overlapping Queries¶
The example above queries the same files twice, with two different patterns,
and this is the whole point of glob().
Because a query owns nothing, any number of glob() calls may match the same file,
from any number of plans.
Declare once with static(), then query as often as convenient.
The NamedGlob object returned by glob() can be iterated directly,
as in the first query,
or converted to a plain list of paths with ng.files(), as in the second one.
More of its features are covered in the Named Glob tutorial.
Changed in StepUp 4
In StepUp 3, overlapping queries were impossible:
glob() declared its matches, so two calls matching the same file raised an error.
Projects worked around this with ordering tricks and non-overlapping patterns.
Reacting to New and Deleted Files¶
Just like a pattern passed to static(),
a glob() pattern is registered with the calling step,
together with the set of matches it produced.
The step is made pending and re-runs when that set changes on a later run,
and is skipped when it does not.
See Patterns Make Steps React to New and Deleted Files
for the details, which are identical for both functions.
Match Static Files Only¶
StepUp will either fail or issue a warning if a glob() match is not declared static
because allowing such matches could lead to non-deterministic builds.
For example, an output file from an initial run could be picked up by a subsequent run
as a match and alter the workflow.
To keep your workflow deterministic,
StepUp requires every match to be declared static by a step or covered by a static tree.
There are two degrees of severity for this check, depending on whether the match is a file that some step builds or not.
Eager Detection – Failure¶
There is a single case in which a glob() match is an outright error:
a matched file that some step builds.
Such a file is not source material and can never become static,
so the pattern is simply wrong and will result in a failed build.
This is reported in either declaration order,
immediately when the pattern is registered or when the output is declared,
whichever comes second.
Any occurrence that slipped through is caught by the end-of-build detection as well,
reported as N glob match(es) are files that a step builds.,
which also set the FAILED bit of the return code.
Narrow the pattern, or declare the file with static() instead of building it.
End-of-build Detection – Warning¶
A glob match that does not match any file in the workflow cannot be judged while the build is running: the plan that would declare it may simply not have run yet. These unchecked matches are settled at the end of the build phase, against whatever the workflow looks like by then. A match that is still unjustified at that point is reported as a warning:
WARNING │ 1 glob match(es) are not declared static.
─────────────────────────────── ./plan.py: *.md ────────────────────────────────
(no node) extra.md
──────────────────────────────────── Remedy ────────────────────────────────────
Every matched file must be declared static or lie inside a static tree.
A matched directory also qualifies when it contains a static file or tree.
Declare the files with static(), or declare their directory as a static tree.
────────────────────────────────────────────────────────────────────────────────
The offending patterns and paths are listed per step,
and the same report is written to .stepup/warning.log.
This is a warning and nothing more: it does not fail the build.
It does set the warning bit (8) of the return code,
so a script can notice it without parsing the log.
The check is also skipped entirely when the build phase already failed for another
reason, because an unjustified match could be a consequence of that failure.
Note that directories can be matched with glob().
However, because directory nodes are not part of the workflow,
a directory match can only be checked at the end of the build phase.
A directory match is considered safe when it lies inside a static tree,
contains one, or contains a static file.
Inherent Risks¶
A glob() pattern can silently under-match in exactly the same way as a static()
pattern: see Inherent Risks.
It is worth stressing that the split between declaring and querying does not make globbing any safer. It only clarifies who owns what. E.g., a pattern that matches three files where you expected four is still a pattern that matches three files, whichever function you pass it to.
Try the Following¶
-
Add a file
src/egg.txtand run StepUp again with the same arguments. The first query gains a match, so./plan.pyre-runs and a copy step is added, without any change toplan.py: the static treesrc/already covers the new file. The concatenation step is skipped, because the second query still matches the same two files. -
Create a file
extra.mdand add a query for it at the end ofplan.py:Nothing uses the match, so no step becomes pending, but nothing declares
extra.mdstatic either. Run StepUp again and read.stepup/warning.log: it contains the report shown above. Note that no step fails:sbexits with return code8, the warning bit, and not with theFAILEDbit. -
Change that query to
glob("*.txt")and run StepUp again. The pattern now matchesnotes.txt, which a step builds, so./plan.pyfails with aGraphErrorexplaining the conflict.