[bazelisk] Use a lock to avoid downloading Bazel lots of times

Before this change, running lots of instances of bazelisk.sh at once
meant downloading lots of copies of Bazel. Oops!

Now, if you run bazelisk.sh on its own on a clean checkout, the first
call to up_to_date will fail. We'll then take a lock, check again that
we need to download bazelisk (we do!) and then download it, finally
releasing the lock.

If we run bazelisk.sh in a clean checkout with 100 threads at once,
they will will all race to get the first flock. One of them will win
the race and follow the steps above. All of the others will sit and
wait for that first thread. Once it is done, they will run in order,
each checking that bazelisk is up to date (it is!) and then do
whatever job they are supposed to be doing.

Of course, the usual situation is that we've already got bazelisk
installed. In this case, the first call to up_to_date will pass and we
won't do any locking at all.

Signed-off-by: Rupert Swarbrick <rswarbrick@lowrisc.org>
diff --git a/bazelisk.sh b/bazelisk.sh
index 45782fd..7c2bfd8 100755
--- a/bazelisk.sh
+++ b/bazelisk.sh
@@ -6,7 +6,7 @@
 # This is a wrapper script for `bazelisk` that downloads and executes bazelisk.
 # Bazelisk is a wrapper for `bazel` that can download and execute the project's
 # required bazel version.
-# 
+#
 # CI jobs should use ci/bazelisk.sh instead, which performs CI-friendly additional
 # setup.
 
@@ -52,10 +52,25 @@
     chmod +x "$file"
 }
 
+function up_to_date() {
+    local file="$1"
+    # We need an update if the file doesn't exist or it has the wrong hash
+    test -f "$file" || return 1
+    check_hash "$file" || return 1
+    return 0
+}
+
 function main() {
-    local file="${REPO_TOP}/${BINDIR}/bazelisk"
-    if [[ ! -f "$file" ]] || ! check_hash "$file"; then
-        prepare
+    local bindir="${REPO_TOP}/${BINDIR}"
+    local file="${bindir}/bazelisk"
+    local lockfile="${bindir}/bazelisk.lock"
+
+    if ! up_to_date "$file"; then
+        # Grab the lock, blocking until success. Upon success, check again
+        # whether we're up to date (because some other process might have
+        # downloaded bazelisk in the meantime). If not, download it ourselves.
+        mkdir -p "$bindir"
+        (flock -x 9; up_to_date "$file" || prepare) 9>>"$lockfile"
     fi
     if ! check_hash "$file"; then
         echo "sha256sum doesn't match expected value"