Add $USE_BAZEL_VERSION and .bazelversion support to Bazel's wrapper script. Also simplify the installation instructions for Ubuntu and how to RELNOTES: Bazel's Debian package and the binary installer now include an improved wrapper that understands `<WORKSPACE>/.bazelversion` files and the `$USE_BAZEL_VERSION` environment variable. This is similar to what Bazelisk offers (https://github.com/bazelbuild/bazelisk#how-does-bazelisk-know-which-bazel-version-to-run-and-where-to-get-it-from), except that it works offline and integrates with apt-get. PiperOrigin-RevId: 283031899
diff --git a/scripts/packages/BUILD b/scripts/packages/BUILD index bcc653b..8a8a4c7 100644 --- a/scripts/packages/BUILD +++ b/scripts/packages/BUILD
@@ -41,6 +41,14 @@ }), ) +filegroup( + name = "wrapper", + srcs = ["bazel.sh"], + visibility = [ + "//src/test/shell/bazel:__subpackages__", + ], +) + sh_binary( name = "package-info-generator", srcs = ["package_info_generator.sh"],
diff --git a/scripts/packages/bazel.sh b/scripts/packages/bazel.sh index b75b2e6..635c059 100755 --- a/scripts/packages/bazel.sh +++ b/scripts/packages/bazel.sh
@@ -1,6 +1,6 @@ #!/bin/bash -# Copyright 2015 The Bazel Authors. All rights reserved. +# Copyright 2019 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. @@ -16,10 +16,37 @@ set -eu -# This is a script which is installed instead of the real Bazel binary. -# It looks for a tools/bazel executable next to the containing WORKSPACE -# file and runs that. If that's not found, it runs the real Bazel binary which -# is installed next to this script as bazel-real. +# This is a script which can be installed as your "bazel" binary instead of the +# real Bazel binary. When called, it tries to determine and run the correct +# Bazel version for a given project and forwards all arguments to it. +# +# You can specify which Bazel version to use using these methods: +# 1. Set $USE_BAZEL_VERSION to a version number +# (e.g. export USE_BAZEL_VERSION=1.0.0). +# 2. Add a .bazelversion file that contains a version number next to your +# WORKSPACE file. +# 3. Otherwise, the latest Bazel version will be used. +# +# This wrapper only recognizes Bazel versions installed next to itself, thus +# if you install this wrapper as /usr/bin/bazel, you'll have to install binaries +# for individual Bazel binaries as e.g. /usr/bin/bazel-1.0.0. +# +# In addition, if an executable called "tools/bazel" is found in the current +# workspace, this script will not directly execute Bazel, but instead store +# the path to the real Bazel executable in the environment variable BAZEL_REAL +# and then execute the "tools/bazel" wrapper script. +# +# In contrast to Bazelisk, this script does not download anything from the +# internet and instead relies on the local system to provide Bazel binaries. + +function color() { + # Usage: color "31;5" "string" + # Some valid values for color: + # - 5 blink, 1 strong, 4 underlined + # - fg: 31 red, 32 green, 33 yellow, 34 blue, 35 purple, 36 cyan, 37 white + # - bg: 40 black, 41 red, 44 blue, 45 purple + printf '\033[%sm%s\033[0m\n' "$@" +} # `readlink -f` that works on OSX too. function get_realpath() { @@ -61,29 +88,99 @@ fi } -BAZEL_REAL="$(dirname "$(get_realpath "${BASH_SOURCE[0]}")")/bazel-real" -export BAZEL_REAL - -WORKSPACE_DIR="${PWD}" -while [[ "${WORKSPACE_DIR}" != / ]]; do - if [[ -e "${WORKSPACE_DIR}/WORKSPACE" ]]; then - break; +function get_workspace_root() { + workspace_dir="${PWD}" + while [[ "${workspace_dir}" != / ]]; do + if [[ -e "${workspace_dir}/WORKSPACE" ]]; then + readonly workspace_dir + return fi - WORKSPACE_DIR="$(dirname "${WORKSPACE_DIR}")" -done -readonly WORKSPACE_DIR + workspace_dir="$(dirname "${workspace_dir}")" + done + readonly workspace_dir="" +} -if [[ -e "${WORKSPACE_DIR}/WORKSPACE" ]]; then - readonly WRAPPER="${WORKSPACE_DIR}/tools/bazel" +get_workspace_root - if [[ -x "${WRAPPER}" && ! -d "${WRAPPER}" ]]; then - exec -a "$0" "${WRAPPER}" "$@" +readonly wrapper_dir="$(dirname "$(get_realpath "${BASH_SOURCE[0]}")")" +readonly os_arch_suffix="$(uname -s | tr '[:upper:]' '[:lower:]')-$(uname -m)" + +function get_bazel_version() { + if [[ -n ${USE_BAZEL_VERSION:-} ]]; then + readonly reason="specified in \$USE_BAZEL_VERSION" + readonly bazel_version="${USE_BAZEL_VERSION}" + elif [[ -e "${workspace_dir}/.bazelversion" ]]; then + readonly reason="specified in ${workspace_dir}/.bazelversion" + read -r bazel_version < "${workspace_dir}/.bazelversion" + readonly bazel_version="${bazel_version}" + elif [[ -x "${wrapper_dir}/bazel-real" ]]; then + readonly reason="automatically selected bazel-real" + readonly bazel_version="real" + else + # Find the latest Bazel version installed on the system. + readonly reason="automatically selected latest available version" + bazel_version="$(basename "$(find -H "${wrapper_dir}" -maxdepth 1 -name 'bazel-[0-9]*-${os_arch_suffix}' -type f | sort -V | tail -n 1)")" + if [[ -z $bazel_version ]]; then + bazel_version="$(basename "$(find -H "${wrapper_dir}" -maxdepth 1 -name 'bazel-[0-9]*' -type f | sort -V | tail -n 1)")" + fi + # Remove the "bazel-" prefix from the file name. + bazel_version="${bazel_version#"bazel-"}" + readonly bazel_version fi +} + +get_bazel_version + +if [[ -z $bazel_version ]]; then + color "31" "ERROR: No installed Bazel version found, cannot continue." + (echo "" + echo "Bazel binaries have to be installed in ${wrapper_dir}, but none were found.") 2>&1 + exit 1 fi -if [[ ! -x "${BAZEL_REAL}" ]]; then - echo "Failed to find underlying Bazel executable at ${BAZEL_REAL}" >&2 - exit 1 +BAZEL_REAL="${wrapper_dir}/bazel-${bazel_version}-${os_arch_suffix}" +if [[ ! -x ${BAZEL_REAL} ]]; then + BAZEL_REAL="${wrapper_dir}/bazel-${bazel_version}" +fi + +if [[ ! -x $BAZEL_REAL ]]; then + color "31" "ERROR: The project you're trying to build requires Bazel ${bazel_version} (${reason}), but it wasn't found in ${wrapper_dir}." + + long_binary_name="bazel-${bazel_version}-${os_arch_suffix}" + + if [[ -x $(command -v apt-get) && $wrapper_dir == "/usr/bin" ]]; then + (echo "" + echo "You can install the required Bazel version via apt:" + echo " sudo apt update && sudo apt install bazel-${bazel_version}" + echo "" + echo "If this doesn't work, check Bazel's installation instructions for help:" + echo " https://docs.bazel.build/versions/master/install-ubuntu.html") 2>&1 + else + (echo "" + echo "Bazel binaries for all official releaeses can be downloaded from here:" + echo " https://github.com/bazelbuild/bazel/releases") 2>&1 + + if [[ -x $(command -v curl) && -w $wrapper_dir ]]; then + (echo "" + echo "You can download the required version directly using this command:" + echo " (cd \"${wrapper_dir}\" && curl -LO https://releases.bazel.build/${bazel_version}/release/${long_binary_name} && chmod +x ${long_binary_name})") 2>&1 + elif [[ -x $(command -v wget) && -w $wrapper_dir ]]; then + (echo "" + echo "You can download the required version directly using this command:" + echo " (cd \"${wrapper_dir}\" && wget https://releases.bazel.build/${bazel_version}/release/${long_binary_name} && chmod +x ${long_binary_name})") 2>&1 + else + (echo "" + echo "Please put the downloaded Bazel binary into this location:" + echo " ${wrapper_dir}/${long_binary_name}") 2>&1 + fi + fi + exit 1 +fi + +readonly wrapper="${workspace_dir}/tools/bazel" +if [[ -x "$wrapper" ]]; then + export BAZEL_REAL + exec -a "$0" "${wrapper}" "$@" fi exec -a "$0" "${BAZEL_REAL}" "$@"
diff --git a/site/docs/install-ubuntu.md b/site/docs/install-ubuntu.md index 2f17b4f..5bf9fca 100644 --- a/site/docs/install-ubuntu.md +++ b/site/docs/install-ubuntu.md
@@ -10,10 +10,14 @@ * 18.04 (LTS) * 16.04 (LTS) +Bazel will probably work fine on other Ubuntu releases and Debian stretch and +above, but we currently do not test this on Bazel's CI and thus can't promise +it. + Install Bazel on Ubuntu using one of the following methods: -* [Use the binary installer (recommended)](#install-with-installer-ubuntu) -* [Use our custom APT repository](#install-on-ubuntu) +* [Use our custom APT repository (recommended)](#install-on-ubuntu) +* [Use the binary installer](#install-with-installer-ubuntu) * [Compile Bazel from source](install-compile-source.md) Bazel comes with two completion scripts. After installing Bazel, you can: @@ -21,19 +25,82 @@ * Access the [bash completion script](completion.md#bash) * Install the [zsh completion script](completion.md#zsh) -<h2 id="install-with-installer-ubuntu">Installing using binary installer</h2> +<h2 id="install-on-ubuntu"> Using Bazel's apt repository</h2> -The binary installers are on Bazel's [GitHub releases page](https://github.com/bazelbuild/bazel/releases). +### Step 1: Add Bazel distribution URI as a package source -The installer contains the Bazel binary. Some additional libraries must also be -installed for Bazel to work. +**Note:** This is a one-time setup step. + +```bash +sudo apt install curl +curl https://bazel.build/bazel-release.pub.gpg | sudo apt-key add - +echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list +``` + +The component name "jdk1.8" is kept for legacy reasons only and doesn't relate +to supported or included JDK versions anymore. In the past, when Bazel did not +yet bundle a private JRE, we had two release versions, one compatible with JDK 7 +and one with JDK 8. However, since we dropped Java 7 support and started +bundling a private runtime, Bazel releases are Java version agnostic. Changing +the "jdk1.8" component name would break existing users of the repo though. + +### Step 2: Install and update Bazel + +```bash +sudo apt update && sudo apt install bazel +``` + +Once installed, you can upgrade to a newer version of Bazel as part of your normal system updates: + +```bash +sudo apt update && sudo apt full-upgrade +``` + +The `bazel` package will always install the latest stable version of Bazel. You +can install specific, older versions of Bazel in addition to the latest one like +this: + +```bash +sudo apt install bazel-1.0.0 +``` + +This will install Bazel 1.0.0 as `/usr/bin/bazel-1.0.0` on your system. This +can be useful if you need a specific version of Bazel to build a project, e.g. +because it uses a `.bazelversion` file to explicitly state with which Bazel +version it should be built. + +### Step 3: Install a JDK (optional) + +Bazel includes a private, bundled JRE as its runtime and doesn't require you to +install any specific version of Java. + +However, if you want to build Java code using Bazel, you have to install a JDK. + +```bash +# Ubuntu 16.04 (LTS) uses OpenJDK 8 by default: +sudo apt install openjdk-8-jdk + +# Ubuntu 18.04 (LTS) uses OpenJDK 11 by default: +sudo apt install openjdk-11-jdk +``` + +<h2 id="install-with-installer-ubuntu">Using the binary installer</h2> + +While we generally recommend to use the apt repository, the binary installer can +be useful in case you don't have admin permissions on your machine or can't add +custom repositories. + +The binary installers can be downloaded from Bazel's [GitHub releases page](https://github.com/bazelbuild/bazel/releases). + +The installer contains the Bazel binary and extracts it into your `$HOME/bin` +folder. Some additional libraries must be installed manually for Bazel to work. ### Step 1: Install required packages Bazel needs a C++ compiler and unzip / zip in order to work: ```bash -sudo apt-get install g++ unzip zip +sudo apt install g++ unzip zip ``` If you want to build Java code using Bazel, install a JDK: @@ -46,14 +113,12 @@ sudo apt-get install openjdk-11-jdk ``` -### Step 2: Download Bazel +### Step 2: Run the installer Next, download the Bazel binary installer named `bazel-<version>-installer-linux-x86_64.sh` from the [Bazel releases page on GitHub](https://github.com/bazelbuild/bazel/releases). -### Step 3: Run the installer - -Run the Bazel installer as follows: +Run it as follows: ```bash chmod +x bazel-<version>-installer-linux-x86_64.sh @@ -64,7 +129,7 @@ sets the `.bazelrc` path to `$HOME/.bazelrc`. Use the `--help` command to see additional installation options. -### Step 4: Set up your environment +### Step 3: Set up your environment If you ran the Bazel installer with the `--user` flag as above, the Bazel executable is installed in your `$HOME/bin` directory. It's a good idea to add @@ -74,51 +139,5 @@ export PATH="$PATH:$HOME/bin" ``` -You can also add this command to your `~/.bashrc` file. - -<h2 id="install-on-ubuntu"> Using Bazel's APT repository</h2> - -### Step 1: Install the JDK (optional) - -Bazel includes a private, bundled JRE as its runtime and doesn't require you to -install any specific version of Java. - -However, if you want to build Java code using Bazel, you have to install a JDK. - -```bash -# Ubuntu 16.04 (LTS) uses OpenJDK 8 by default: -sudo apt-get install openjdk-8-jdk - -# Ubuntu 18.04 (LTS) uses OpenJDK 11 by default: -sudo apt-get install openjdk-11-jdk -``` - -### Step 2: Add Bazel distribution URI as a package source - -**Note:** This is a one-time setup step. - -```bash -sudo apt-get install curl -curl https://bazel.build/bazel-release.pub.gpg | sudo apt-key add - -echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list -``` - -Note: The component name "jdk1.8" is kept for legacy reasons only and doesn't -relate to supported or included JDK versions anymore. In the past, when Bazel -did not yet bundle a private JRE, we had two release versions, one compatible -with JDK 7 and one with JDK 8. However, since we dropped Java 7 support and -started bundling a private runtime, Bazel releases are Java version agnostic. -Changing the "jdk1.8" component name would break existing users of the repo -though. - -### Step 3: Install and update Bazel - -```bash -sudo apt-get update && sudo apt-get install bazel -``` - -Once installed, you can upgrade to a newer version of Bazel with the following command: - -```bash -sudo apt-get install --only-upgrade bazel -``` +You can also add this command to your `~/.bashrc` or `~/.zshrc` file to make it +permanent.
diff --git a/src/test/shell/bazel/BUILD b/src/test/shell/bazel/BUILD index 95c1777..1a2efa5 100644 --- a/src/test/shell/bazel/BUILD +++ b/src/test/shell/bazel/BUILD
@@ -145,6 +145,19 @@ ) sh_test( + name = "bazel_wrapper_test", + srcs = ["bazel_wrapper_test.sh"], + data = [ + ":test-deps", + "//scripts/packages:wrapper", + "@bazel_tools//tools/bash/runfiles", + ], + tags = [ + "no_windows", # wrapper is not used on Windows + ], +) + +sh_test( name = "bazel_java_test_no_windows", size = "large", timeout = "eternal",
diff --git a/src/test/shell/bazel/bazel_wrapper_test.sh b/src/test/shell/bazel/bazel_wrapper_test.sh new file mode 100755 index 0000000..e8366d6 --- /dev/null +++ b/src/test/shell/bazel/bazel_wrapper_test.sh
@@ -0,0 +1,207 @@ +#!/bin/bash +# +# Copyright 2019 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. + +# An end-to-end test for the wrapper used by our installer and distro packages. + +# --- begin runfiles.bash initialization --- +if [[ ! -d "${RUNFILES_DIR:-/dev/null}" && ! -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then + if [[ -f "$0.runfiles_manifest" ]]; then + export RUNFILES_MANIFEST_FILE="$0.runfiles_manifest" + elif [[ -f "$0.runfiles/MANIFEST" ]]; then + export RUNFILES_MANIFEST_FILE="$0.runfiles/MANIFEST" + elif [[ -f "$0.runfiles/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then + export RUNFILES_DIR="$0.runfiles" + fi +fi +if [[ -f "${RUNFILES_DIR:-/dev/null}/bazel_tools/tools/bash/runfiles/runfiles.bash" ]]; then + source "${RUNFILES_DIR}/bazel_tools/tools/bash/runfiles/runfiles.bash" +elif [[ -f "${RUNFILES_MANIFEST_FILE:-/dev/null}" ]]; then + source "$(grep -m1 "^bazel_tools/tools/bash/runfiles/runfiles.bash " \ + "$RUNFILES_MANIFEST_FILE" | cut -d ' ' -f 2-)" +else + echo >&2 "ERROR: cannot find @bazel_tools//tools/bash/runfiles:runfiles.bash" + exit 1 +fi +# --- end runfiles.bash initialization --- + +source "$(rlocation "io_bazel/src/test/shell/integration_test_setup.sh")" \ + || { echo "integration_test_setup.sh not found!" >&2; exit 1; } + +set -e + +wrapper=$(rlocation io_bazel/scripts/packages/bazel.sh) + +mock_bazel() { + cat > "$1" <<'EOF' +#!/bin/bash +set -euo pipefail +echo "Hello from $(basename $0)!" +echo "My args: $@" +exit 0 +EOF + chmod +x "$1" +} + +setup_mock() { + # Setup a mock workspace. + mkdir ws ws/subdir + touch ws/{BUILD,WORKSPACE} + touch ws/subdir/BUILD + + # Setup a mock "/usr/bin" folder with the wrapper and some "Bazel" binaries. + mkdir bin + cp "$wrapper" "bin/bazel" + chmod +x "bin/bazel" + mock_bazel "bin/bazel-0.29.1" + mock_bazel "bin/bazel-1.0.1" + mock_bazel "bin/bazel-real" + + cd ws +} + +test_use_bazel_version_envvar() { + setup_mock + + USE_BAZEL_VERSION="0.29.1" ../bin/bazel version &> "$TEST_log" + expect_log "Hello from bazel-0.29.1" + expect_log "My args: version" +} + +test_bazelversion_file() { + setup_mock + + echo "1.0.1" > .bazelversion + + ../bin/bazel info &> "$TEST_log" + expect_log "Hello from bazel-1.0.1" + expect_log "My args: info" + + cd subdir + ../../bin/bazel build //src:bazel &> "$TEST_log" + expect_log "Hello from bazel-1.0.1" + expect_log "My args: build //src:bazel" +} + +test_uses_bazelreal() { + setup_mock + + ../bin/bazel &> "$TEST_log" + expect_log "Hello from bazel-real" + expect_log "My args:" +} + +test_uses_latest_version() { + setup_mock + + rm ../bin/bazel-real + ../bin/bazel &> "$TEST_log" + expect_log "Hello from bazel-1.0.1" + + rm ../bin/bazel-1.0.1 + ../bin/bazel &> "$TEST_log" + expect_log "Hello from bazel-0.29.1" +} + +test_error_message_for_no_available_bazel_version() { + setup_mock + + rm ../bin/bazel-* + if ../bin/bazel &> "$TEST_log"; then + fail "Bazel wrapper should have failed" + fi + expect_log "No installed Bazel version found, cannot continue" +} + +test_error_message_for_required_bazel_not_found() { + setup_mock + + if USE_BAZEL_VERSION="foobar" ../bin/bazel &> "$TEST_log"; then + fail "Bazel wrapper should have failed" + fi + expect_log "ERROR: The project you're trying to build requires Bazel foobar (specified in \$USE_BAZEL_VERSION)" +} + +test_recommends_curl() { + setup_mock + + mkdir mockpath + for bin in uname readlink dirname tr; do + ln -s "$(command -v $bin)" mockpath + done + touch mockpath/curl + chmod +x mockpath/curl + + if PATH="$(pwd)/mockpath" USE_BAZEL_VERSION="foobar" ../bin/bazel &> "$TEST_log"; then + fail "Bazel wrapper should have failed" + fi + expect_log "curl -LO" + expect_not_log "wget" +} + +test_recommends_wget() { + setup_mock + + mkdir mockpath + for bin in uname readlink dirname tr; do + ln -s "$(command -v $bin)" mockpath + done + touch mockpath/wget + chmod +x mockpath/wget + + if PATH="$(pwd)/mockpath" USE_BAZEL_VERSION="foobar" ../bin/bazel &> "$TEST_log"; then + fail "Bazel wrapper should have failed" + fi + expect_log "wget" + expect_not_log "curl" +} + +test_recommends_manual_download() { + setup_mock + + mkdir mockpath + for bin in uname readlink dirname tr; do + ln -s "$(command -v $bin)" mockpath + done + + if PATH="$(pwd)/mockpath" USE_BAZEL_VERSION="foobar" ../bin/bazel &> "$TEST_log"; then + fail "Bazel wrapper should have failed" + fi + expect_log "Please put the downloaded Bazel binary into this location" + expect_not_log "curl" + expect_not_log "wget" +} + +test_delegates_to_wrapper_if_present() { + setup_mock + + mkdir tools + cat > tools/bazel <<'EOF' +#!/bin/bash +set -euo pipefail +echo "Hello from the wrapper tools/bazel!" +echo "BAZEL_REAL = ${BAZEL_REAL}" +echo "My args: $@" +exit 0 +EOF + chmod +x tools/bazel + + USE_BAZEL_VERSION="0.29.1" ../bin/bazel build //src:bazel &> "$TEST_log" + expect_log "Hello from the wrapper tools/bazel!" + expect_log "BAZEL_REAL = .*/bin/bazel-0.29.1" + expect_log "My args: build //src:bazel" +} + +run_suite "Integration tests for scripts/packages/bazel.sh."