diff options
author | Laszlo Csomor <laszlocsomor@google.com> | 2018-02-05 05:24:34 -0800 |
---|---|---|
committer | Copybara-Service <copybara-piper@google.com> | 2018-02-05 05:25:54 -0800 |
commit | a610a2b77893ed9edd3038cffe803bce68f83a80 (patch) | |
tree | e24dddc2d457ffdb908b3326f05748876c9955ef | |
parent | 17830c25a816d67ba79e783acaa6a085b516bd43 (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.py | 1 | ||||
-rw-r--r-- | src/main/java/com/google/devtools/build/lib/bazel/rules/python/python_stub_template.txt | 31 | ||||
-rw-r--r-- | src/test/py/bazel/BUILD | 6 | ||||
-rw-r--r-- | src/test/py/bazel/runfiles_test.py | 44 | ||||
-rw-r--r-- | src/test/py/bazel/test_base.py | 27 | ||||
-rw-r--r-- | src/test/py/bazel/testdata/runfiles_test/WORKSPACE.mock | 1 | ||||
-rw-r--r-- | src/test/py/bazel/testdata/runfiles_test/foo/BUILD.mock | 7 | ||||
-rw-r--r-- | src/test/py/bazel/testdata/runfiles_test/foo/datadep/hello.txt | 1 | ||||
-rw-r--r-- | src/test/py/bazel/testdata/runfiles_test/foo/runfiles.py | 21 | ||||
-rw-r--r-- | src/tools/runfiles/BUILD | 13 | ||||
-rw-r--r-- | src/tools/runfiles/runfiles.py | 203 | ||||
-rw-r--r-- | src/tools/runfiles/runfiles_test.py | 135 | ||||
-rw-r--r-- | tools/runfiles/BUILD.tools | 5 |
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"], +) |