Skip to content

Commit

Permalink
shell,runfiles: add runfiles library
Browse files Browse the repository at this point in the history
Create sourceable runfiles.sh that implements
rlocation and a couple other helper functions for
shell scripts.

Change-Id: Ifcf9ee86ed63afe2ce655be596ac781494660398

RELNOTES[NEW]: runfiles, sh: Shell scripts may now depend on //src/tools/runfiles:runfiles_sh_lib and source runfiles.sh. The script defines the `rlocation` function which returns runfile paths on every platform.

PiperOrigin-RevId: 171816037
  • Loading branch information
laszlocsomor authored and hlopko committed Oct 11, 2017
1 parent 52f32bf commit 0cb8d40
Show file tree
Hide file tree
Showing 5 changed files with 351 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ py_binary(
"//src/java_tools/buildjar/java/com/google/devtools/build/buildjar/jarhelper:srcs",
"//src/tools/android/java/com/google/devtools/build/android:embedded_tools",
"//src/tools/launcher:srcs",
"//src/tools/runfiles:srcs",
"//src/tools/singlejar:embedded_tools",
"//src/main/cpp/util:embedded_tools",
"//src/main/native:embedded_tools",
Expand Down Expand Up @@ -416,6 +417,7 @@ filegroup(
"//src/tools/android/java/com/google/devtools/build/android:srcs",
"//src/tools/benchmark:srcs",
"//src/tools/launcher:srcs",
"//src/tools/runfiles:srcs",
"//src/tools/skylark/java/com/google/devtools/skylark/skylint:srcs",
"//src/tools/skylark/javatests/com/google/devtools/skylark/skylint:srcs",
"//src/tools/xcode/actoolwrapper:srcs",
Expand Down Expand Up @@ -470,6 +472,7 @@ test_suite(
"//src/test/shell/bazel:bazel_bootstrap_distfile_test",
"//src/test/shell/bazel:bazel_windows_example_test",
"//src/tools/launcher:all_windows_tests",
"//src/tools/runfiles:all_windows_tests",
"//third_party/def_parser:all_windows_tests",
],
)
46 changes: 46 additions & 0 deletions src/tools/runfiles/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package(default_visibility = ["//visibility:private"])

exports_files(
["runfiles.sh"],
visibility = ["//visibility:public"],
)

filegroup(
name = "srcs",
srcs = glob(["**"]),
visibility = ["//src:__pkg__"],
)

sh_library(
name = "runfiles_sh_lib",
srcs = ["runfiles.sh"],
visibility = ["//visibility:public"],
)

sh_test(
name = "runfiles_sh_test",
srcs = select({
"//src:windows": ["runfiles_windows_test.sh"],
"//src:windows_msys": ["runfiles_windows_test.sh"],
"//src:windows_msvc": ["runfiles_windows_test.sh"],
"//conditions:default": ["runfiles_posix_test.sh"],
}),
deps = [":runfiles_sh_lib"],
)

test_suite(
name = "windows_tests",
tags = [
"-no_windows",
"-slow",
],
visibility = ["//visibility:private"],
)

test_suite(
name = "all_windows_tests",
tests = [
":windows_tests",
],
visibility = ["//src:__pkg__"],
)
128 changes: 128 additions & 0 deletions src/tools/runfiles/runfiles.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#!/bin/bash
#
# Copyright 2017 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# This script defines utility functions to handle sh_binary runfiles.
#
# On Windows, this script needs $RUNFILES_MANIFEST_FILE to point to the absolute
# path of the runfiles manifest file. If the envvar is undefined or empty, this
# script calls "exit 1".
#
# On Linux/macOS, this script needs $RUNFILES_DIR to point to the absolute path
# of the runfiles directory. If the envvar is undefined or empty, this script
# tries to determine the value by looking for the nearest "*.runfiles" parent
# directory of "$0", and if not found, this script calls "exit 1".

set -eu

# Check that we can find the bintools, otherwise we would see confusing errors.
stat "$0" >&/dev/null || {
echo >&2 "ERROR[runfiles.sh]: cannot locate GNU coreutils; check your PATH."
echo >&2 " You may need to run 'export PATH=/bin:/usr/bin:\$PATH' (on Linux/macOS)"
echo >&2 " or 'set PATH=c:\\tools\\msys64\\usr\\bin;%PATH%' (on Windows)."
exit 1
}

# Now that we have bintools on PATH, determine the current platform and define
# `is_windows` accordingly.
case "$(uname -s | tr [:upper:] [:lower:])" in
msys*|mingw*|cygwin*)
function is_windows() {
true
}
;;
*)
function is_windows() {
false
}
;;
esac
export -f is_windows

# Define `is_absolute` unless already defined.
if ! type is_absolute &>/dev/null; then
function is_absolute() {
if is_windows; then
echo "$1" | grep -q "^[a-zA-Z]:[/\\]"
else
[[ "$1" = /* ]]
fi
}
export -f is_absolute
fi

# Define `rlocation` unless already defined.
if ! type rlocation &>/dev/null; then
if is_windows; then
# If RUNFILES_MANIFEST_FILE is empty/undefined, bail out.
# On Windows there's no runfiles tree with symlinks like on Linux/macOS, so
# we cannot locate the runfiles root and the manifest by walking the path
# of $0.
if [[ -z "${RUNFILES_MANIFEST_FILE:-}" ]]; then
echo >&2 "ERROR[runfiles.sh]: RUNFILES_MANIFEST_FILE is empty/undefined"
exit 1
fi

# Read the runfiles manifest to memory, to quicken runfiles lookups.
# First, read each line of the manifest into `runfiles_lines`. We need to do
# this while IFS is still the newline character. In the subsequent loop,
# after we reset IFS, we can construct the `line_split` arrays.
old_ifs="${IFS:-}"
IFS=$'\n'
runfiles_lines=( $(sed -e 's/\r//g' "$RUNFILES_MANIFEST_FILE") )
IFS="$old_ifs"
# Now create a dictionary from `runfiles_lines`. Creating `line_split` uses
# $IFS so we could not have done this without a helper array.
declare -A runfiles_dict
for line in "${runfiles_lines[@]}"; do
line_split=($line)
runfiles_dict[${line_split[0]}]="${line_split[@]:1}"
done
else
# If RUNFILES_DIR is empty/undefined, try locating the runfiles directory.
# When the user runs a sh_binary's output directly, it's just a symlink to
# the main script. There's no launcher like on Windows which would set this
# environment variable.
# Walk up the path of $0 looking for a runfiles directory.
if [[ -z "${RUNFILES_DIR:-}" ]]; then
RUNFILES_DIR="$(dirname "$0")"
while [[ "$RUNFILES_DIR" != "/" ]]; do
if [[ "$RUNFILES_DIR" = *.runfiles ]]; then
break
else
RUNFILES_DIR="$(dirname "$RUNFILES_DIR")"
fi
done
if [[ "$RUNFILES_DIR" = "/" ]]; then
echo >&2 "ERROR[runfiles.sh]: RUNFILES_DIR is empty/undefined, and cannot find a"
echo >&2 " runfiles directory on the path of this script"
exit 1
fi
fi
fi

function rlocation() {
if is_absolute "$1"; then
echo "$1"
else
if is_windows; then
echo "${runfiles_dict[$1]}"
else
echo "${RUNFILES_DIR}/$1"
fi
fi
}
export -f rlocation
fi
80 changes: 80 additions & 0 deletions src/tools/runfiles/runfiles_posix_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/bin/bash
#
# Copyright 2017 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -eu

function _log_base() {
prefix=$1
shift
echo >&2 "${prefix}[$(basename "$0") $(date "+%H:%M:%S.%N (%z)")] $@"
}

function log_fatal() {
_log_base "ERROR" "$@"
exit 1
}

function fail() {
_log_base "FAILED" "$@"
exit 1
}

stat "$0" >&/dev/null || log_fatal "cannot locate GNU coreutils"

# Unset existing definitions of the functions we want to test.
if type rlocation >&/dev/null; then
unset is_absolute
unset is_windows
unset rlocation
fi
if rlocation >&/dev/null; then
fail "rlocation is still defined"
fi

# Find runfiles.sh
runfiles_sh=$(dirname $0)/runfiles.sh
[[ -e "$runfiles_sh" ]] || fail "cannot find '$runfiles_sh'"

# Assert that runfiles.sh attempts to look up the runfiles directory.
# It will find the actual runfiles directory of this test.
unset RUNFILES_DIR
source "$runfiles_sh" || fail "cannot source '$runfiles_sh'"
[[ "$RUNFILES_DIR" = *.runfiles ]] \
|| fail "'$runfiles_sh' cannot find the runfiles directory"

# Set a mock $RUNFILES_DIR.
# Unset `rlocation` so runfiles.sh will define it again.
export RUNFILES_DIR="/path/to runfiles"
unset is_absolute
source "$runfiles_sh" || fail "cannot source '$runfiles_sh'"

# Exercise the functions in runfiles.sh.
if is_windows; then
fail "expected is_windows() to be false"
fi

if is_absolute "d:/foo"; then
fail "expected d:/foo not to be absolute"
fi
if is_absolute "D:\\foo"; then
fail "expected D:\\foo not to be absolute"
fi
is_absolute "/foo" || fail "expected /foo to be absolute"

[[ "$(rlocation "some/runfile")" = "/path/to runfiles/some/runfile" ]] \
|| fail "rlocation 1 failed"
[[ "$(rlocation "/some absolute/runfile")" = "/some absolute/runfile" ]] \
|| fail "rlocation 2 failed"
94 changes: 94 additions & 0 deletions src/tools/runfiles/runfiles_windows_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/bin/bash
#
# Copyright 2017 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

set -eu

if ! stat "$0" >&/dev/null; then
echo >&2 "ERROR[runfiles_windows_test.sh] cannot locate GNU coreutils"
exit 1
fi

function _log_base() {
prefix=$1
shift
echo >&2 "${prefix}[$(basename "$0") $(date "+%H:%M:%S.%N (%z)")] $@"
}

function log_fatal() {
_log_base "ERROR" "$@"
exit 1
}

function fail() {
_log_base "FAILED" "$@"
exit 1
}

# Look up runfiles.sh manually, do not rely on rlocation being already defined.
# If all is working well, then rlocation should already be defined, because the
# native launcher of the sh_test already sourced runfiles.sh from @bazel_tools,
# but this test exercises runfiles.sh itself (from HEAD).
[ -n "${RUNFILES_MANIFEST_FILE:-}" ] \
|| log_fatal "RUNFILES_MANIFEST_FILE is undefined or empty"
runfiles_sh="$(cat "$RUNFILES_MANIFEST_FILE" \
| fgrep "io_bazel/src/tools/runfiles/runfiles.sh" \
| cut -d' ' -f2-)"
[ -n "$runfiles_sh" ] || fail "cannot find runfiles.sh"

# Unset existing definitions of the functions we want to test.
if type rlocation >&/dev/null; then
unset is_absolute
unset is_windows
unset rlocation
fi
if type rlocation >&/dev/null; then
fail "rlocation is still defined"
fi

# Assert that runfiles.sh needs $RUNFILES_MANIFEST_FILE.
unset RUNFILES_MANIFEST_FILE
if (source "$runfiles_sh" >&/dev/null) then
fail "should fail to source '$runfiles_sh'"
fi

# Set a mock $RUNFILES_MANIFEST_FILE.
export RUNFILES_MANIFEST_FILE="$TEST_TMPDIR/mock-runfiles.txt"
cat >"$RUNFILES_MANIFEST_FILE" <<'end_of_manifest'
runfile/without/absolute/path
runfile/spaceless c:\path\to\runfile1
runfile/spaceful c:\path\to\runfile with spaces
end_of_manifest

# Source runfiles.sh and exercise its functions.
source "$runfiles_sh" || fail "cannot source '${runfiles_sh}'"

# Exercise the functions in runfiles.sh.
is_windows || fail "expected is_windows() to be true"

is_absolute "d:/foo" || fail "expected d:/foo to be absolute"
is_absolute "D:\\foo" || fail "expected D:\\foo to be absolute"
if is_absolute "/foo"; then
fail "expected /foo not to be absolute"
fi

[[ -z "$(rlocation runfile/without/absolute/path)" ]] \
|| fail "rlocation 1 failed"
[[ "$(rlocation runfile/spaceless)" = "c:\\path\\to\\runfile1" ]] \
|| fail "rlocation 2 failed"
[[ "$(rlocation runfile/spaceful)" = "c:\\path\\to\\runfile with spaces" ]] \
|| fail "rlocation 3 failed"
[[ "$(rlocation "c:\\some absolute/path")" = "c:\\some absolute/path" ]] \
|| fail "rlocation 4 failed"

0 comments on commit 0cb8d40

Please sign in to comment.