Update the flutter script's locking mechanism and follow_links (#57590)
Update the flutter and dart scripts' locking mechanism and follow_links function to be more robust and support more platforms.
This adds support for using mkdir as a fallback if the system doesn't have flock instead of using shlock, since shlock doesn't work on shared filesystems.
It also fixes a problem in the follow_links function where it failed when the link resolved to the root directory.
diff --git a/bin/dart b/bin/dart
index a2d317f..22a56cb 100755
--- a/bin/dart
+++ b/bin/dart
@@ -13,28 +13,37 @@
set -e
+# Needed because if it is set, cd may print the path it changed to.
unset CDPATH
-function follow_links() {
- cd -P "${1%/*}"
- local file="$PWD/${1##*/}"
+# On Mac OS, readlink -f doesn't work, so follow_links traverses the path one
+# link at a time, and then cds into the link destination and find out where it
+# ends up.
+#
+# The returned filesystem path must be a format usable by Dart's URI parser,
+# since the Dart command line tool treats its argument as a file URI, not a
+# filename. For instance, multiple consecutive slashes should be reduced to a
+# single slash, since double-slashes indicate a URI "authority", and these are
+# supposed to be filenames. There is an edge case where this will return
+# multiple slashes: when the input resolves to the root directory. However, if
+# that were the case, we wouldn't be running this shell, so we don't do anything
+# about it.
+#
+# The function is enclosed in a subshell to avoid changing the working directory
+# of the caller.
+function follow_links() (
+ cd -P "$(dirname -- "$1")"
+ file="$PWD/$(basename -- "$1")"
while [[ -h "$file" ]]; do
- # On Mac OS, readlink -f doesn't work.
- cd -P "${file%/*}"
- file="$(readlink "$file")"
- cd -P "${file%/*}"
- file="$PWD/${file##*/}"
+ cd -P "$(dirname -- "$file")"
+ file="$(readlink -- "$file")"
+ cd -P "$(dirname -- "$file")"
+ file="$PWD/$(basename -- "$file")"
done
- echo "$PWD/${file##*/}"
-}
+ echo "$file"
+)
-# Convert a filesystem path to a format usable by Dart's URI parser.
-function path_uri() {
- # Reduce multiple leading slashes to a single slash.
- echo "$1" | sed -E -e "s,^/+,/,"
-}
-
-PROG_NAME="$(path_uri "$(follow_links "$BASH_SOURCE")")"
+PROG_NAME="$(follow_links "$BASH_SOURCE")"
BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
# To define `shared::execute()` function
diff --git a/bin/flutter b/bin/flutter
index 89ec060..6d03899 100755
--- a/bin/flutter
+++ b/bin/flutter
@@ -13,28 +13,37 @@
set -e
+# Needed because if it is set, cd may print the path it changed to.
unset CDPATH
-function follow_links() {
- cd -P "${1%/*}"
- local file="$PWD/${1##*/}"
+# On Mac OS, readlink -f doesn't work, so follow_links traverses the path one
+# link at a time, and then cds into the link destination and find out where it
+# ends up.
+#
+# The returned filesystem path must be a format usable by Dart's URI parser,
+# since the Dart command line tool treats its argument as a file URI, not a
+# filename. For instance, multiple consecutive slashes should be reduced to a
+# single slash, since double-slashes indicate a URI "authority", and these are
+# supposed to be filenames. There is an edge case where this will return
+# multiple slashes: when the input resolves to the root directory. However, if
+# that were the case, we wouldn't be running this shell, so we don't do anything
+# about it.
+#
+# The function is enclosed in a subshell to avoid changing the working directory
+# of the caller.
+function follow_links() (
+ cd -P "$(dirname -- "$1")"
+ file="$PWD/$(basename -- "$1")"
while [[ -h "$file" ]]; do
- # On Mac OS, readlink -f doesn't work.
- cd -P "${file%/*}"
- file="$(readlink "$file")"
- cd -P "${file%/*}"
- file="$PWD/${file##*/}"
+ cd -P "$(dirname -- "$file")"
+ file="$(readlink -- "$file")"
+ cd -P "$(dirname -- "$file")"
+ file="$PWD/$(basename -- "$file")"
done
- echo "$PWD/${file##*/}"
-}
+ echo "$file"
+)
-# Convert a filesystem path to a format usable by Dart's URI parser.
-function path_uri() {
- # Reduce multiple leading slashes to a single slash.
- echo "$1" | sed -E -e "s,^/+,/,"
-}
-
-PROG_NAME="$(path_uri "$(follow_links "$BASH_SOURCE")")"
+PROG_NAME="$(follow_links "$BASH_SOURCE")"
BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
# To define `shared::execute()` function
diff --git a/bin/shared.sh b/bin/shared.sh
index 22a5b98..a3769a1 100755
--- a/bin/shared.sh
+++ b/bin/shared.sh
@@ -14,12 +14,9 @@
set -e
+# Needed because if it is set, cd may print the path it changed to.
unset CDPATH
-function _rmlock () {
- [ -n "$FLUTTER_UPGRADE_LOCK" ] && rm -f "$FLUTTER_UPGRADE_LOCK"
-}
-
function retry_upgrade {
local total_tries="10"
local remaining_tries=$((total_tries - 1))
@@ -37,50 +34,78 @@
return 0
}
-function upgrade_flutter () {
+# Trap function for removing any remaining lock file at exit.
+function _rmlock () {
+ [ -n "$FLUTTER_UPGRADE_LOCK" ] && rm -rf "$FLUTTER_UPGRADE_LOCK"
+}
+
+# Determines which lock method to use, based on what is available on the system.
+# Returns a non-zero value if the lock was not acquired, zero if acquired.
+function _lock () {
+ if hash flock 2>/dev/null; then
+ flock --nonblock --exclusive 7 2>/dev/null
+ else
+ mkdir "$1" 2>/dev/null
+ fi
+}
+
+# Waits for an update lock to be acquired.
+#
+# To ensure that we don't simultaneously update Dart in multiple parallel
+# instances, we try to obtain an exclusive lock on this file descriptor (and
+# thus this script's source file) while we are updating Dart and compiling the
+# script. To do this, we try to use the command line program "flock", which is
+# available on many Unix-like platforms, in particular on most Linux
+# distributions. You give it a file descriptor, and it locks the corresponding
+# file, having inherited the file descriptor from the shell.
+#
+# Complicating matters, there are two major scenarios where this will not
+# work.
+#
+# The first is if the platform doesn't have "flock", for example on macOS. There
+# is not a direct equivalent, so on platforms that don't have flock, we fall
+# back to using mkdir as an atomic operation to create a lock directory. If
+# mkdir is able to create the directory, then the lock is acquired. To determine
+# if we have "flock" available, we use the "hash" shell built-in.
+#
+# The second complication is NFS. On NFS, to obtain an exclusive lock you need a
+# file descriptor that is open for writing. Thus, we ignore errors from flock by
+# redirecting all output to /dev/null, since users will typically not care about
+# errors from flock and are more likely to be confused by them than helped.
+#
+# The upgrade_flutter function calling _wait_for_lock is executed in a subshell
+# with a redirect that pipes the source of this script into file descriptor 7.
+# A flock lock is released when this subshell exits and file descriptor 7 is
+# closed. The mkdir lock is released via an exit trap from the subshell that
+# deletes the lock directory.
+function _wait_for_lock () {
+ FLUTTER_UPGRADE_LOCK="$FLUTTER_ROOT/bin/cache/.upgrade_lock"
+ local waiting_message_displayed
+ while ! _lock "$FLUTTER_UPGRADE_LOCK"; do
+ if [[ -z $waiting_message_displayed ]]; then
+ # Print with a return so that if the Dart code also prints this message
+ # when it does its own lock, the message won't appear twice. Be sure that
+ # the clearing printf below has the same number of space characters.
+ printf "Waiting for another flutter command to release the startup lock...\r";
+ waiting_message_displayed="true"
+ fi
+ sleep .1;
+ done
+ # Clear the waiting message so it doesn't overlap any following text.
+ printf " \r";
+ unset waiting_message_displayed
+ # If the lock file is acquired, make sure that it is removed on exit.
+ trap _rmlock INT TERM EXIT
+}
+
+# This function is always run in a subshell. Running the function in a subshell
+# is required to make sure any lock directory is cleaned up by the exit trap in
+# _wait_for_lock.
+function upgrade_flutter () (
mkdir -p "$FLUTTER_ROOT/bin/cache"
- # This function is executed with a redirect that pipes the source of
- # this script into file descriptor 3.
- #
- # To ensure that we don't simultaneously update Dart in multiple
- # parallel instances, we try to obtain an exclusive lock on this
- # file descriptor (and thus this script's source file) while we are
- # updating Dart and compiling the script. To do this, we try to use
- # the command line program "flock", which is available on many
- # Unix-like platforms, in particular on most Linux distributions.
- # You give it a file descriptor, and it locks the corresponding
- # file, having inherited the file descriptor from the shell.
- #
- # Complicating matters, there are two major scenarios where this
- # will not work.
- #
- # The first is if the platform doesn't have "flock", for example on Mac.
- # There is not a direct equivalent, so on platforms that don't have flock,
- # we fall back to using a lockfile and spinlock with "shlock". This
- # doesn't work as well over NFS as it relies on PIDs. Any platform
- # without either of these tools has no locking at all. To determine if we
- # have "flock" or "shlock" available, we abuse the "hash" shell built-in.
- #
- # The second complication is NFS. On NFS, to obtain an exclusive
- # lock you need a file descriptor that is open for writing, because
- # NFS implements exclusive locks by writing, or some such. Thus, we
- # ignore errors from flock. We do so by using the '|| true' trick,
- # since we are running in a 'set -e' environment wherein all errors
- # are fatal, and by redirecting all output to /dev/null, since
- # users will typically not care about errors from flock and are
- # more likely to be confused by them than helped.
- #
- # For "flock", the lock is released when the file descriptor goes out of
- # scope, i.e. when this function returns. The lock is released via
- # a trap when using "shlock".
- if hash flock 2>/dev/null; then
- flock 3 2>/dev/null || true
- elif hash shlock 2>/dev/null; then
- FLUTTER_UPGRADE_LOCK="$FLUTTER_ROOT/bin/cache/.upgrade_lock"
- while ! shlock -f "$FLUTTER_UPGRADE_LOCK" -p $$ ; do sleep .1 ; done
- trap _rmlock EXIT
- fi
+ # Waits for the update lock to be acquired.
+ _wait_for_lock
local revision="$(cd "$FLUTTER_ROOT"; git rev-parse HEAD)"
@@ -111,15 +136,15 @@
"$DART" --disable-dart-dev $FLUTTER_TOOL_ARGS --snapshot="$SNAPSHOT_PATH" --packages="$FLUTTER_TOOLS_DIR/.packages" --no-enable-mirrors "$SCRIPT_PATH"
echo "$revision" > "$STAMP_PATH"
fi
- # The exit here is duplicitous since the function is run in a subshell,
- # but this serves as documentation that running the function in a
- # subshell is required to make sure any lockfile created by shlock
- # is cleaned up.
+ # The exit here is extraneous since the function is run in a subshell, but
+ # this serves as documentation that running the function in a subshell is
+ # required to make sure any lock directory created by mkdir is cleaned up.
exit $?
-}
+)
# This function is intended to be executed by entrypoints (e.g. `//bin/flutter`
-# and `//bin/dart`)
+# and `//bin/dart`). PROG_NAME and BIN_DIR should already be set by those
+# entrypoints.
function shared::execute() {
export FLUTTER_ROOT="$(cd "${BIN_DIR}/.." ; pwd -P)"
@@ -132,8 +157,8 @@
DART="$DART_SDK_PATH/bin/dart"
PUB="$DART_SDK_PATH/bin/pub"
- # If running over git-bash, overrides the default UNIX
- # executables with win32 executables
+ # If running over git-bash, overrides the default UNIX executables with win32
+ # executables
case "$(uname -s)" in
MINGW32*)
DART="$DART.exe"
@@ -159,8 +184,8 @@
if [[ ! -e "$FLUTTER_ROOT/.git" ]]; then
echo "Error: The Flutter directory is not a clone of the GitHub project."
echo " The flutter tool requires Git in order to operate properly;"
- echo " to set up Flutter, run the following command:"
- echo " git clone -b stable https://github.com/flutter/flutter.git"
+ echo " to install Flutter, see the instructions at:"
+ echo " https://flutter.dev/get-started"
exit 1
fi
@@ -169,13 +194,13 @@
# FLUTTER_TOOL_ARGS="--enable-asserts $FLUTTER_TOOL_ARGS"
# FLUTTER_TOOL_ARGS="$FLUTTER_TOOL_ARGS --observe=65432"
- (upgrade_flutter) 3< "$PROG_NAME"
+ upgrade_flutter 7< "$PROG_NAME"
BIN_NAME="$(basename "$PROG_NAME")"
case "$BIN_NAME" in
flutter*)
- # FLUTTER_TOOL_ARGS aren't quoted below, because it is meant to
- # be considered as separate space-separated args.
+ # FLUTTER_TOOL_ARGS aren't quoted below, because it is meant to be
+ # considered as separate space-separated args.
"$DART" --disable-dart-dev --packages="$FLUTTER_TOOLS_DIR/.packages" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@"
;;
dart*)
diff --git a/packages/flutter_tools/test/commands.shard/permeable/test_test.dart b/packages/flutter_tools/test/commands.shard/permeable/test_test.dart
index 5300af1..1c9f74a 100644
--- a/packages/flutter_tools/test/commands.shard/permeable/test_test.dart
+++ b/packages/flutter_tools/test/commands.shard/permeable/test_test.dart
@@ -218,7 +218,7 @@
expect(exec.exitCode, exitCode);
final List<String> output = (exec.stdout as String).split('\n');
- if (output.first == 'Waiting for another flutter command to release the startup lock...') {
+ if (output.first.startsWith('Waiting for another flutter command to release the startup lock...')) {
output.removeAt(0);
}
if (output.first.startsWith('Running "flutter pub get" in')) {