infra_virtualenv: Introduce python_venv.

This change will introduce a new script `python_venv` which
transparently executes a Python interpreter in a virtualenv,
possibly creating a new virtualenv if it has not been created
yet.

This script is a better replacement to `find_virtualenv.sh`
because:

1. `python_venv` does not require to be sourced from bash.
   This allows any program (e.g. Python) to run a Python
   interpreter in a virtualenv. Also it makes it easy to run
   an interactive Python shell in a virtualenv, which is
   convenient to manually inspect the virtualenv environment
   for debug.

2. `python_venv` can be called from any working directory,
   which makes the caller code simpler.

BUG=chromium:733103
TEST=bin/run_tests
TEST=bin/turtle

Change-Id: I94e85a3f3e5ec31d29094e273f0ff9466a8405a2
Reviewed-on: https://chromium-review.googlesource.com/563279
Commit-Ready: Shuhei Takahashi <nya@chromium.org>
Tested-by: Shuhei Takahashi <nya@chromium.org>
Reviewed-by: Shuhei Takahashi <nya@chromium.org>
diff --git a/README.md b/README.md
index 21252ed..87f3428 100644
--- a/README.md
+++ b/README.md
@@ -14,8 +14,7 @@
    directory will be importable inside the virtualenv.
 2. Create a `requirements.txt` file inside `venv` to list external
    packages to install.
-3. Copy `bin/find_virtualenv.sh` and `bin/turtle` which serve as
-   templates.
+3. Copy `bin/python_venv` and `bin/turtle` which serve as templates.
 
 Adding pre-built packages
 -------------------------
@@ -29,10 +28,10 @@
 Low level API
 -------------
 
-The `create_venv` script prepares a virtualenv using a
+The `bin/create_venv` script prepares a virtualenv using a
 `requirements.txt` file.
 
-    $ create_venv requirements.txt
+    $ bin/create_venv requirements.txt
 
 The script will print the path to the virtualenv to stdout.  Note that
 the output ends with a newline; Bash handles this, but Python does
@@ -43,7 +42,7 @@
 
 Together, this might look up:
 
-    $ venv=$(create_venv requirements.txt)
+    $ venv=$(bin/create_venv requirements.txt)
     $ ${venv}/bin/python
 
 NOTE: it is not generally safe to run the other scripts in the
diff --git a/bin/find_virtualenv.sh b/bin/find_virtualenv.sh
deleted file mode 100644
index e40081a..0000000
--- a/bin/find_virtualenv.sh
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/bash
-# Copyright 2017 The Chromium OS Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-#
-# Find the virtualenv repo to use.
-#
-# This is meant to be sourced in scripts that need virtualenv.  The
-# sourcing script should cd into the directory containing this script
-# first.  This script defines functions for performing common tasks:
-#
-# exec_python_module -- Execute Python module inside of virtualenv
-#
-# This script is a canonical template that can be copied to other
-# repositories.  The venv_repo variable should be changed to point to
-# this repository.
-set -eu
-
-realpath() {
-    readlink -f -- "$1"
-}
-
-# venv_repo should be changed if this script is copied to other repos.
-readonly venv_repo=$(realpath ..)
-readonly create_script=$(realpath "${venv_repo}/bin/create_venv")
-readonly venv_home=$(realpath ../venv)
-readonly reqs_file=$(realpath "${venv_home}/requirements.txt")
-
-exec_python_module() {
-    venvdir=$("${create_script}" "$reqs_file")
-    export PYTHONPATH=${venv_home}
-    exec "${venvdir}/bin/python" -m "$@"
-}
diff --git a/bin/python_venv b/bin/python_venv
new file mode 100755
index 0000000..c4f6b87
--- /dev/null
+++ b/bin/python_venv
@@ -0,0 +1,74 @@
+#!/bin/bash
+# Copyright 2017 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+#
+# Starts a python interpreter in virtualenv.
+#
+# This script will set up a virtualenv when it has not been created yet and
+# executes the Python interpreter.
+#
+# This script is a canonical template that can be copied to other
+# repositories. The infra_virtualenv_path variable should be changed to point
+# to this repository.
+
+set -eu
+
+# Change this constant to the path(s) to infra_virtualenv directory when you
+# copy this script to other repos.
+# A path can be a relative path from this script, or an absolute path. If this
+# array contains multiple paths, they are searched in the listed order.
+readonly -a infra_virtualenv_paths=(
+    ".."
+)
+
+readonly bin_dir="$(readlink -e -- "$(dirname -- "$0")")"
+if [[ ! -d "${bin_dir}" ]]; then
+    echo "ERROR: Can not locate the location of python_env!" >&2
+    exit 1
+fi
+
+realpath() {
+    pushd "${bin_dir}" > /dev/null 2>&1
+    readlink -e -- "$1"
+    popd > /dev/null 2>&1
+}
+
+find_create_venv() {
+    local p
+    for p in "${infra_virtualenv_paths[@]}"; do
+        local create_venv=$(realpath "${p}/bin/create_venv")
+        if [[ -f "${create_venv}" ]]; then
+            echo "${create_venv}"
+            break
+        fi
+    done
+}
+
+readonly create_venv=$(find_create_venv)
+if [[ ! -f "${create_venv}" ]]; then
+    cat <<EOF >&2
+ERROR: create_venv script could not be located.
+You need to update a constant inside python_venv, or your checkout might be
+incomplete.
+EOF
+    exit 1
+fi
+
+readonly extra_imports_dir=$(realpath ../venv)
+if [[ ! -d "${extra_imports_dir}" ]]; then
+    cat <<EOF >&2
+ERROR: venv directory is not found at ${bin_dir}/..
+See infra_virtualenv/README.md for details.
+EOF
+    exit 1
+fi
+
+readonly venv_dir=$("${create_venv}" "${extra_imports_dir}/requirements.txt")
+if [[ ! -d "${venv_dir}" ]]; then
+    echo "ERROR: Failed to set up a virtualenv." >&2
+    exit 1
+fi
+
+export PYTHONPATH="${extra_imports_dir}"
+exec "${venv_dir}/bin/python" "$@"
diff --git a/bin/run_tests b/bin/run_tests
index 851e5bc..967e7df 100755
--- a/bin/run_tests
+++ b/bin/run_tests
@@ -6,9 +6,9 @@
 # Run unit tests.
 set -eu
 
-cd -- "$(readlink -e -- "$(dirname -- "$0")")"
-. ./find_virtualenv.sh
-cd ../venv
+readonly bin_dir="$(readlink -e -- "$(dirname -- "$0")")"
+
+cd "${bin_dir}/../venv"
 
 find . -name "*.pyc" -print0 | xargs -0 rm -f
 while getopts v name; do
@@ -18,4 +18,4 @@
     esac
 done
 
-exec_python_module unittest discover "${verbose:+-v}" -s cros_venv
+exec "${bin_dir}/python_venv" -m unittest discover "${verbose:+-v}" -s cros_venv
diff --git a/bin/turtle b/bin/turtle
index 4c491ef..4b8d314 100755
--- a/bin/turtle
+++ b/bin/turtle
@@ -9,9 +9,6 @@
 # repositories.
 set -eu
 
-original=$(pwd)
-cd -- "$(readlink -e -- "$(dirname -- "$0")")"
-. ./find_virtualenv.sh
-cd -- "$original"
+readonly bin_dir="$(readlink -e -- "$(dirname -- "$0")")"
 
-exec_python_module turtle "$@"
+exec "${bin_dir}/python_venv" -m turtle "$@"