The problem with "block upstream" or "block downstream" is that you're always blocking something which could be doing work.
If you use "git", you can do something along these lines - which happens to be what I am doing...
I use a tracking branch which points to the most recent finished build job of any step, named as follows: <branch>-latest-<step>
. So, if you run a "build" step based on master, you would get master-latest-build
. It is very easy to move this branch towards the end of your build script, just run: git branch -f <name> HEAD
, followed by a push.
I then have the downstream jobs trigger off of that tracking branch. This way, all the jobs are loosely coupled and will do the right thing on whatever the upstream job produced, independent of what that upstream job is currently working on.
If, in addition to that, you also tag your build, your downstream job can retrieve the tag and re-use it as the build name to allow you to easily correlate various runs.
This is very effective if the pipeline has steps of vastly differing length - especially when your downstream steps take much longer than your upstream steps, which in my world is the norm, as the downstream tests include a whole suite of performance and integration tests...