aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Laszlo Csomor <laszlocsomor@google.com>2018-02-05 05:24:34 -0800
committerGravatar Copybara-Service <copybara-piper@google.com>2018-02-05 05:25:54 -0800
commita610a2b77893ed9edd3038cffe803bce68f83a80 (patch)
treee24dddc2d457ffdb908b3326f05748876c9955ef
parent17830c25a816d67ba79e783acaa6a085b516bd43 (diff)
python,runfiles: runfiles library in @bazel_tools
Also update the Python stub script template to set $RUNFILES_MANIFEST_FILE or $RUNFILES_DIR so the runfiles library only needs to look for those. See https://github.com/bazelbuild/bazel/issues/4460 RELNOTES[NEW]: python,runfiles: You can now depend on `@bazel_tools//tools/runfiles:py-runfiles` to get a platform-independent runfiles library for Python. See DocString of https://github.com/bazelbuild/bazel/blob/master/src/tools/runfiles/runfiles.py for usage information. Change-Id: I4f68a11cb59f2782e5203e39fe60cc66b46023a2 PiperOrigin-RevId: 184515490
-rw-r--r--src/create_embedded_tools.py1
-rw-r--r--src/main/java/com/google/devtools/build/lib/bazel/rules/python/python_stub_template.txt31
-rw-r--r--src/test/py/bazel/BUILD6
-rw-r--r--src/test/py/bazel/runfiles_test.py44
-rw-r--r--src/test/py/bazel/test_base.py27
-rw-r--r--src/test/py/bazel/testdata/runfiles_test/WORKSPACE.mock1
-rw-r--r--src/test/py/bazel/testdata/runfiles_test/foo/BUILD.mock7
-rw-r--r--src/test/py/bazel/testdata/runfiles_test/foo/datadep/hello.txt1
-rw-r--r--src/test/py/bazel/testdata/runfiles_test/foo/runfiles.py21
-rw-r--r--src/tools/runfiles/BUILD13
-rw-r--r--src/tools/runfiles/runfiles.py203
-rw-r--r--src/tools/runfiles/runfiles_test.py135
-rw-r--r--tools/runfiles/BUILD.tools5
13 files changed, 491 insertions, 4 deletions
diff --git a/src/create_embedded_tools.py b/src/create_embedded_tools.py
index 352069c411..0bcbdefe21 100644
--- a/src/create_embedded_tools.py
+++ b/src/create_embedded_tools.py
@@ -44,6 +44,7 @@ output_paths = [
lambda x: 'tools/jdk/ExperimentalTestRunner_deploy.jar'),
('*Runner_deploy.jar', lambda x: 'tools/jdk/TestRunner_deploy.jar'),
('*singlejar', lambda x: 'tools/jdk/singlejar/singlejar'),
+ ('src/tools/runfiles/runfiles.py', lambda x: 'tools/runfiles/runfiles.py'),
('*launcher.exe', lambda x: 'tools/launcher/launcher.exe'),
('*def_parser.exe', lambda x: 'tools/def_parser/def_parser.exe'),
('*ijar.exe', lambda x: 'tools/jdk/ijar/ijar.exe'),
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/python_stub_template.txt b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/python_stub_template.txt
index 84dd571afa..d2cdccc6fd 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/rules/python/python_stub_template.txt
+++ b/src/main/java/com/google/devtools/build/lib/bazel/rules/python/python_stub_template.txt
@@ -100,6 +100,34 @@ def GetRepositoriesImports(module_space, import_all):
return [d for d in repo_dirs if os.path.isdir(d)]
return [os.path.join(module_space, "%workspace_name%")]
+# Finds the runfiles manifest or the runfiles directory.
+def RunfilesEnvvar(module_space):
+ # If this binary is the data-dependency of another one, the other sets
+ # RUNFILES_MANIFEST_FILE or RUNFILES_DIR for our sake.
+ runfiles = os.environ.get('RUNFILES_MANIFEST_FILE', None)
+ if runfiles:
+ return ('RUNFILES_MANIFEST_FILE', runfiles)
+
+ runfiles = os.environ.get('RUNFILES_DIR', None)
+ if runfiles:
+ return ('RUNFILES_DIR', runfiles)
+
+ # If running from a zip, there's no manifest file.
+ if IsRunningFromZip():
+ return ('RUNFILES_DIR', module_space)
+
+ # Look for the runfiles "output" manifest, argv[0] + ".runfiles_manifest"
+ runfiles = module_space + '_manifest'
+ if os.path.exists(runfiles):
+ return ('RUNFILES_MANIFEST_FILE', runfiles)
+
+ # Look for the runfiles "input" manifest, argv[0] + ".runfiles/MANIFEST"
+ runfiles = os.path.join(module_space, 'MANIFEST')
+ if os.path.exists(runfiles):
+ return ('RUNFILES_DIR', runfiles)
+
+ return (None, None)
+
def Main():
args = sys.argv[1:]
@@ -125,6 +153,9 @@ def Main():
python_path = python_path.replace("/", os.sep)
new_env['PYTHONPATH'] = python_path
+ runfiles_envkey, runfiles_envvalue = RunfilesEnvvar(module_space)
+ if runfiles_envkey:
+ new_env[runfiles_envkey] = runfiles_envvalue
# Now look for my main python source file.
# The magic string percent-main-percent is replaced with the filename of the
diff --git a/src/test/py/bazel/BUILD b/src/test/py/bazel/BUILD
index cfaf121925..87f3eef26f 100644
--- a/src/test/py/bazel/BUILD
+++ b/src/test/py/bazel/BUILD
@@ -95,7 +95,11 @@ py_test(
py_test(
name = "runfiles_test",
srcs = ["runfiles_test.py"],
- deps = [":test_base"],
+ data = glob(["testdata/runfiles_test/**"]),
+ deps = [
+ ":test_base",
+ "//third_party/py/six",
+ ],
)
py_test(
diff --git a/src/test/py/bazel/runfiles_test.py b/src/test/py/bazel/runfiles_test.py
index da6a143be4..c965f38fba 100644
--- a/src/test/py/bazel/runfiles_test.py
+++ b/src/test/py/bazel/runfiles_test.py
@@ -15,6 +15,7 @@
import os
import unittest
+import six
from src.test.py.bazel import test_base
@@ -70,13 +71,50 @@ class RunfilesTest(test_base.TestBase):
exit_code, stdout, stderr = self.RunProgram([bin_path])
self.AssertExitCode(exit_code, 0, stderr)
if len(stdout) != 2:
- self.fail("stdout: " + stdout)
+ self.fail("stdout: %s" % stdout)
self.assertEqual(stdout[0], "Hello Foo!")
- self.assertRegexpMatches(stdout[1], "^rloc=.*/foo/bar/hello.txt")
+ six.assertRegex(self, stdout[1], "^rloc=.*/foo/bar/hello.txt")
with open(stdout[1].split("=", 1)[1], "r") as f:
lines = [l.strip() for l in f.readlines()]
if len(lines) != 1:
- self.fail("lines: " + lines)
+ self.fail("lines: %s" % lines)
+ self.assertEqual(lines[0], "world")
+
+ def testPythonRunfilesLibraryInBazelToolsRepo(self):
+ for s, t in [
+ ("WORKSPACE.mock", "WORKSPACE"),
+ ("foo/BUILD.mock", "foo/BUILD"),
+ ("foo/runfiles.py", "foo/runfiles.py"),
+ ("foo/datadep/hello.txt", "foo/datadep/hello.txt"),
+ ]:
+ self.CopyFile(
+ self.Rlocation(
+ "io_bazel/src/test/py/bazel/testdata/runfiles_test/" + s), t)
+
+ exit_code, stdout, stderr = self.RunBazel(["info", "bazel-bin"])
+ self.AssertExitCode(exit_code, 0, stderr)
+ bazel_bin = stdout[0]
+
+ exit_code, _, stderr = self.RunBazel(["build", "//foo:runfiles-py"])
+ self.AssertExitCode(exit_code, 0, stderr)
+
+ if test_base.TestBase.IsWindows():
+ bin_path = os.path.join(bazel_bin, "foo/runfiles-py.exe")
+ else:
+ bin_path = os.path.join(bazel_bin, "foo/runfiles-py")
+
+ self.assertTrue(os.path.exists(bin_path))
+
+ exit_code, stdout, stderr = self.RunProgram([bin_path])
+ self.AssertExitCode(exit_code, 0, stderr)
+ if len(stdout) != 2:
+ self.fail("stdout: %s" % stdout)
+ self.assertEqual(stdout[0], "Hello Foo!")
+ six.assertRegex(self, stdout[1], "^rloc=.*/foo/datadep/hello.txt")
+ with open(stdout[1].split("=", 1)[1], "r") as f:
+ lines = [l.strip() for l in f.readlines()]
+ if len(lines) != 1:
+ self.fail("lines: %s" % lines)
self.assertEqual(lines[0], "world")
diff --git a/src/test/py/bazel/test_base.py b/src/test/py/bazel/test_base.py
index be8a0c4eb9..27d1898ab7 100644
--- a/src/test/py/bazel/test_base.py
+++ b/src/test/py/bazel/test_base.py
@@ -174,6 +174,33 @@ class TestBase(unittest.TestCase):
os.chmod(abspath, stat.S_IRWXU)
return abspath
+ def CopyFile(self, src_path, dst_path, executable=False):
+ """Copy a file to a path under the test's scratch directory.
+
+ Args:
+ src_path: string; a path, the file to copy
+ dst_path: string; a path, relative to the test's scratch directory, the
+ destination to copy the file to, e.g. "foo/bar/BUILD"
+ executable: bool; whether to make the destination file executable
+ Returns:
+ The absolute path of the destination file.
+ Raises:
+ ArgumentError: if `dst_path` is absolute or contains uplevel references
+ IOError: if an I/O error occurs
+ """
+ if not src_path or not dst_path:
+ return
+ abspath = self.Path(dst_path)
+ if os.path.exists(abspath) and not os.path.isfile(abspath):
+ raise IOError('"%s" (%s) exists and is not a file' % (dst_path, abspath))
+ self.ScratchDir(os.path.dirname(dst_path))
+ with open(src_path, 'r') as s:
+ with open(abspath, 'w') as d:
+ d.write(s.read())
+ if executable:
+ os.chmod(abspath, stat.S_IRWXU)
+ return abspath
+
def RunBazel(self, args, env_remove=None, env_add=None):
"""Runs "bazel <args>", waits for it to exit.
diff --git a/src/test/py/bazel/testdata/runfiles_test/WORKSPACE.mock b/src/test/py/bazel/testdata/runfiles_test/WORKSPACE.mock
new file mode 100644
index 0000000000..e7c87450e1
--- /dev/null
+++ b/src/test/py/bazel/testdata/runfiles_test/WORKSPACE.mock
@@ -0,0 +1 @@
+workspace(name = "foo_ws")
diff --git a/src/test/py/bazel/testdata/runfiles_test/foo/BUILD.mock b/src/test/py/bazel/testdata/runfiles_test/foo/BUILD.mock
new file mode 100644
index 0000000000..db28159133
--- /dev/null
+++ b/src/test/py/bazel/testdata/runfiles_test/foo/BUILD.mock
@@ -0,0 +1,7 @@
+py_binary(
+ name = "runfiles-py",
+ srcs = ["runfiles.py"],
+ data = ["datadep/hello.txt"],
+ main = "runfiles.py",
+ deps = ["@bazel_tools//tools/runfiles:py-runfiles"],
+)
diff --git a/src/test/py/bazel/testdata/runfiles_test/foo/datadep/hello.txt b/src/test/py/bazel/testdata/runfiles_test/foo/datadep/hello.txt
new file mode 100644
index 0000000000..cc628ccd10
--- /dev/null
+++ b/src/test/py/bazel/testdata/runfiles_test/foo/datadep/hello.txt
@@ -0,0 +1 @@
+world
diff --git a/src/test/py/bazel/testdata/runfiles_test/foo/runfiles.py b/src/test/py/bazel/testdata/runfiles_test/foo/runfiles.py
new file mode 100644
index 0000000000..c4f3def874
--- /dev/null
+++ b/src/test/py/bazel/testdata/runfiles_test/foo/runfiles.py
@@ -0,0 +1,21 @@
+# Copyright 2018 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.
+"""Mock Python binary, only used in tests."""
+
+from __future__ import print_function
+from bazel_tools.tools.runfiles import runfiles
+
+print('Hello Foo!')
+r = runfiles.Create()
+print('rloc=%s' % r.Rlocation('foo_ws/foo/datadep/hello.txt'))
diff --git a/src/tools/runfiles/BUILD b/src/tools/runfiles/BUILD
index e0ba09f1a6..5ac490d1e2 100644
--- a/src/tools/runfiles/BUILD
+++ b/src/tools/runfiles/BUILD
@@ -20,11 +20,24 @@ filegroup(
name = "embedded_tools",
srcs = [
"BUILD.tools",
+ "runfiles.py",
"//src/tools/runfiles/java/com/google/devtools/build/runfiles:embedded_tools",
],
visibility = ["//src:__pkg__"],
)
+py_library(
+ name = "py-runfiles",
+ srcs = ["runfiles.py"],
+)
+
+py_test(
+ name = "py-runfiles-test",
+ srcs = ["runfiles_test.py"],
+ main = "runfiles_test.py",
+ deps = [":py-runfiles"],
+)
+
sh_library(
name = "runfiles_sh_lib",
srcs = ["runfiles.sh"],
diff --git a/src/tools/runfiles/runfiles.py b/src/tools/runfiles/runfiles.py
new file mode 100644
index 0000000000..0a9149b559
--- /dev/null
+++ b/src/tools/runfiles/runfiles.py
@@ -0,0 +1,203 @@
+# Copyright 2018 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.
+"""Runfiles lookup library for Bazel-built Python binaries and tests.
+
+Usage:
+
+from bazel_tools.tools.runfiles import runfiles
+
+r = runfiles.Create()
+with open(r.Rlocation("io_bazel/foo/bar.txt"), "r") as f:
+ contents = f.readlines()
+
+The code above creates a manifest- or directory-based implementations based on
+the environment variables in os.environ. See `Create()` for more info.
+
+If you want to explicitly create a manifest- or directory-based
+implementations, you can do so as follows:
+
+ r1 = runfiles.CreateManifestBased("path/to/foo.runfiles_manifest")
+
+ r2 = runfiles.CreateDirectoryBased("path/to/foo.runfiles/")
+
+If you want to start subprocesses that also need runfiles, you need to set the
+right environment variables for them:
+
+ import subprocess
+ from bazel_tools.tools.runfiles import runfiles
+
+ r = runfiles.Create()
+ env = {}
+ ...
+ env.update(r.EnvVar())
+ p = subprocess.Popen([r.Rlocation("path/to/binary")], env, ...)
+"""
+
+import os
+import posixpath
+
+
+def CreateManifestBased(manifest_path):
+ return _Runfiles(_ManifestBased(manifest_path))
+
+
+def CreateDirectoryBased(runfiles_dir_path):
+ return _Runfiles(_DirectoryBased(runfiles_dir_path))
+
+
+def Create(env=None):
+ """Returns a new `Runfiles` instance.
+
+ The returned object is either:
+ - manifest-based, meaning it looks up runfile paths from a manifest file, or
+ - directory-based, meaning it looks up runfile paths under a given directory
+ path
+
+ If `env` contains "RUNFILES_MANIFEST_FILE" with non-empty value, this method
+ returns a manifest-based implementation. The object eagerly reads and caches
+ the whole manifest file upon instantiation; this may be relevant for
+ performance consideration.
+
+ Otherwise, if `env` contains "RUNFILES_DIR" or "TEST_SRCDIR" with non-empty
+ value (checked in this priority order), this method returns a directory-based
+ implementation.
+
+ If neither cases apply, this method returns null.
+
+ Args:
+ env: {string: string}; optional; the map of environment variables. If None,
+ this function uses the environment variable map of this process.
+ Raises:
+ IOError: if some IO error occurs.
+ """
+ env_map = os.environ if env is None else env
+ manifest = env_map.get("RUNFILES_MANIFEST_FILE")
+ if manifest:
+ return CreateManifestBased(manifest)
+
+ directory = env_map.get("RUNFILES_DIR")
+ if not directory:
+ directory = env_map.get("TEST_SRCDIR")
+ if directory:
+ return CreateDirectoryBased(directory)
+
+ return None
+
+
+class _Runfiles(object):
+ """Returns the runtime location of runfiles.
+
+ Runfiles are data-dependencies of Bazel-built binaries and tests.
+ """
+
+ def __init__(self, strategy):
+ self._strategy = strategy
+
+ def Rlocation(self, path):
+ """Returns the runtime path of a runfile.
+
+ Runfiles are data-dependencies of Bazel-built binaries and tests.
+
+ The returned path may not be valid. The caller should check the path's
+ validity and that the path exists.
+
+ The function may return None. In that case the caller can be sure that the
+ rule does not know about this data-dependency.
+
+ Args:
+ path: string; runfiles-root-relative path of the runfile
+ Returns:
+ the path to the runfile, which the caller should check for existence, or
+ None if the method doesn't know about this runfile
+ Raises:
+ TypeError: if `path` is not a string
+ ValueError: if `path` is None or empty, or it's absolute or contains
+ uplevel references
+ """
+ if not path:
+ raise ValueError()
+ if not isinstance(path, str):
+ raise TypeError()
+ if ".." in path:
+ raise ValueError("path contains uplevel references: \"%s\"" % path)
+ if os.path.isabs(path) or path[0] == os.sep:
+ raise ValueError("path is absolute: \"%s\"" % path)
+ return self._strategy.RlocationChecked(path)
+
+ def EnvVar(self):
+ """Returns an environment variable for subprocesses.
+
+ The caller should set the returned key-value pair in the environment of
+ subprocesses in case those subprocesses are also Bazel-built binaries that
+ need to use runfiles.
+
+ Returns:
+ {string: string}; a single-entry dict; key is an environment variable
+ name (either "RUNFILES_MANIFEST_FILE" or "RUNFILES_DIR"), value is the
+ value for this environment variable
+ """
+ return self._strategy.EnvVar()
+
+
+class _ManifestBased(object):
+ """`Runfiles` strategy that parses a runfiles-manifest to look up runfiles."""
+
+ def __init__(self, path):
+ if not path:
+ raise ValueError()
+ if not isinstance(path, str):
+ raise TypeError()
+ self._path = path
+ self._runfiles = _ManifestBased._LoadRunfiles(path)
+
+ def RlocationChecked(self, path):
+ return self._runfiles.get(path)
+
+ @staticmethod
+ def _LoadRunfiles(path):
+ """Loads the runfiles manifest."""
+ result = {}
+ with open(path, "r") as f:
+ for line in f:
+ line = line.strip()
+ if line:
+ tokens = line.split(" ", 1)
+ if len(tokens) == 1:
+ result[line] = line
+ else:
+ result[tokens[0]] = tokens[1]
+ return result
+
+ def EnvVar(self):
+ return {"RUNFILES_MANIFEST_FILE": self._path}
+
+
+class _DirectoryBased(object):
+ """`Runfiles` strategy that appends runfiles paths to the runfiles root."""
+
+ def __init__(self, path):
+ if not path:
+ raise ValueError()
+ if not isinstance(path, str):
+ raise TypeError()
+ self._runfiles_root = path
+
+ def RlocationChecked(self, path):
+ # Use posixpath instead of os.path, because Bazel only creates a runfiles
+ # tree on Unix platforms, so `Create()` will only create a directory-based
+ # runfiles strategy on those platforms.
+ return posixpath.join(self._runfiles_root, path)
+
+ def EnvVar(self):
+ return {"RUNFILES_DIR": self._runfiles_root}
diff --git a/src/tools/runfiles/runfiles_test.py b/src/tools/runfiles/runfiles_test.py
new file mode 100644
index 0000000000..10c1b8e061
--- /dev/null
+++ b/src/tools/runfiles/runfiles_test.py
@@ -0,0 +1,135 @@
+# pylint: disable=g-bad-file-header
+# Copyright 2018 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.
+
+import os
+import tempfile
+import unittest
+
+from src.tools.runfiles import runfiles
+
+
+class RunfilesTest(unittest.TestCase):
+ # """Unit tests for `runfiles.Runfiles`."""
+
+ def testRlocationArgumentValidation(self):
+ r = runfiles.Create({"RUNFILES_DIR": "whatever"})
+ self.assertRaises(ValueError, lambda: r.Rlocation(None))
+ self.assertRaises(ValueError, lambda: r.Rlocation(""))
+ self.assertRaises(TypeError, lambda: r.Rlocation(1))
+ self.assertRaisesRegexp(ValueError, "contains uplevel",
+ lambda: r.Rlocation("foo/.."))
+ if RunfilesTest.IsWindows():
+ self.assertRaisesRegexp(ValueError, "is absolute",
+ lambda: r.Rlocation("\\foo"))
+ self.assertRaisesRegexp(ValueError, "is absolute",
+ lambda: r.Rlocation("c:/foo"))
+ self.assertRaisesRegexp(ValueError, "is absolute",
+ lambda: r.Rlocation("c:\\foo"))
+ else:
+ self.assertRaisesRegexp(ValueError, "is absolute",
+ lambda: r.Rlocation("/foo"))
+
+ def testCreatesManifestBasedRunfiles(self):
+ with _MockFile(["a/b c/d"]) as mf:
+ r = runfiles.Create({
+ "RUNFILES_MANIFEST_FILE": mf.Path(),
+ "RUNFILES_DIR": "ignored when RUNFILES_MANIFEST_FILE has a value",
+ "TEST_SRCDIR": "ignored when RUNFILES_MANIFEST_FILE has a value"
+ })
+ self.assertEqual(r.Rlocation("a/b"), "c/d")
+ self.assertIsNone(r.Rlocation("foo"))
+ self.assertDictEqual(r.EnvVar(), {"RUNFILES_MANIFEST_FILE": mf.Path()})
+
+ def testCreatesDirectoryBasedRunfiles(self):
+ r = runfiles.Create({
+ "RUNFILES_DIR": "runfiles/dir",
+ "TEST_SRCDIR": "ignored when RUNFILES_DIR is set"
+ })
+ self.assertEqual(r.Rlocation("a/b"), "runfiles/dir/a/b")
+ self.assertEqual(r.Rlocation("foo"), "runfiles/dir/foo")
+ self.assertDictEqual(r.EnvVar(), {"RUNFILES_DIR": "runfiles/dir"})
+
+ r = runfiles.Create({"TEST_SRCDIR": "test/srcdir"})
+ self.assertEqual(r.Rlocation("a/b"), "test/srcdir/a/b")
+ self.assertEqual(r.Rlocation("foo"), "test/srcdir/foo")
+ self.assertDictEqual(r.EnvVar(), {"RUNFILES_DIR": "test/srcdir"})
+
+ def testFailsToCreateManifestBasedBecauseManifestDoesNotExist(self):
+
+ def _Run():
+ runfiles.Create({"RUNFILES_MANIFEST_FILE": "non-existing path"})
+
+ self.assertRaisesRegexp(IOError, "non-existing path", _Run)
+
+ def testFailsToCreateAnyRunfilesBecauseEnvvarsAreNotDefined(self):
+ with _MockFile(["a b"]) as mf:
+ runfiles.Create({
+ "RUNFILES_MANIFEST_FILE": mf.Path(),
+ "RUNFILES_DIR": "whatever",
+ "TEST_SRCDIR": "whatever"
+ })
+ runfiles.Create({"RUNFILES_DIR": "whatever", "TEST_SRCDIR": "whatever"})
+ runfiles.Create({"TEST_SRCDIR": "whatever"})
+ self.assertIsNone(runfiles.Create({"FOO": "bar"}))
+
+ def testManifestBasedRlocation(self):
+ with _MockFile([
+ "Foo/runfile1", "Foo/runfile2 C:/Actual Path\\runfile2",
+ "Foo/Bar/runfile3 D:\\the path\\run file 3.txt"
+ ]) as mf:
+ r = runfiles.CreateManifestBased(mf.Path())
+ self.assertEqual(r.Rlocation("Foo/runfile1"), "Foo/runfile1")
+ self.assertEqual(r.Rlocation("Foo/runfile2"), "C:/Actual Path\\runfile2")
+ self.assertEqual(
+ r.Rlocation("Foo/Bar/runfile3"), "D:\\the path\\run file 3.txt")
+ self.assertIsNone(r.Rlocation("unknown"))
+ self.assertDictEqual(r.EnvVar(), {"RUNFILES_MANIFEST_FILE": mf.Path()})
+
+ def testDirectoryBasedRlocation(self):
+ # The _DirectoryBased strategy simply joins the runfiles directory and the
+ # runfile's path on a "/". This strategy does not perform any normalization,
+ # nor does it check that the path exists.
+ r = runfiles.CreateDirectoryBased("foo/bar baz//qux/")
+ self.assertEqual(r.Rlocation("arg"), "foo/bar baz//qux/arg")
+ self.assertDictEqual(r.EnvVar(), {"RUNFILES_DIR": "foo/bar baz//qux/"})
+
+ @staticmethod
+ def IsWindows():
+ return os.name == "nt"
+
+
+class _MockFile(object):
+
+ def __init__(self, contents):
+ self._contents = contents
+ self._path = None
+
+ def __enter__(self):
+ tmpdir = os.environ.get("TEST_TMPDIR")
+ self._path = os.path.join(tempfile.mkdtemp(dir=tmpdir), "x")
+ with open(self._path, "wt") as f:
+ f.writelines(l + "\n" for l in self._contents)
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ os.remove(self._path)
+ os.rmdir(os.path.dirname(self._path))
+
+ def Path(self):
+ return self._path
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tools/runfiles/BUILD.tools b/tools/runfiles/BUILD.tools
index d92852377b..c50d0f73d3 100644
--- a/tools/runfiles/BUILD.tools
+++ b/tools/runfiles/BUILD.tools
@@ -4,3 +4,8 @@ alias(
name = "java-runfiles",
actual = "//src/tools/runfiles/java/com/google/devtools/build/runfiles",
)
+
+py_library(
+ name = "py-runfiles",
+ srcs = ["runfiles.py"],
+)