diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fc00ca25f..aa30313674 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,8 @@ END_UNRELEASED_TEMPLATE * (rules) {obj}`pip_compile` now generates a `.test` target. The `_test` target is deprecated and will be removed in the next major release. ([#2794](https://github.com/bazel-contrib/rules_python/issues/2794) +* (runfiles) Fall back to directory based runfiles using relative paths if runfiles + cannot be found using environment variables. {#v0-0-0-fixed} ### Fixed diff --git a/python/runfiles/runfiles.py b/python/runfiles/runfiles.py index 3943be5646..020adedabb 100644 --- a/python/runfiles/runfiles.py +++ b/python/runfiles/runfiles.py @@ -323,6 +323,9 @@ def Create(env: Optional[Dict[str, str]] = None) -> Optional["Runfiles"]: Otherwise, if `env` contains "RUNFILES_DIR" with non-empty value (checked in this priority order), this method returns a directory-based implementation. + Otherwise, if a valid runfiles folder is found relative to this file, this + method returns a directory-based implementation. + If neither cases apply, this method returns null. Args: @@ -339,6 +342,10 @@ def Create(env: Optional[Dict[str, str]] = None) -> Optional["Runfiles"]: directory = env_map.get("RUNFILES_DIR") if directory: return CreateDirectoryBased(directory) + + directory = _FindPythonRunfilesRoot() + if os.path.exists(os.path.join(directory, "_repo_mapping")): + return CreateDirectoryBased(directory) return None diff --git a/tests/runfiles/BUILD.bazel b/tests/runfiles/BUILD.bazel index 5c92026082..19eb0140e1 100644 --- a/tests/runfiles/BUILD.bazel +++ b/tests/runfiles/BUILD.bazel @@ -1,6 +1,7 @@ load("@bazel_skylib//rules:build_test.bzl", "build_test") load("@rules_python//python:py_test.bzl", "py_test") load("@rules_python//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED") # buildifier: disable=bzl-visibility +load("//tests/support:sh_py_run_test.bzl", "sh_py_run_test") py_test( name = "runfiles_test", @@ -11,6 +12,14 @@ py_test( deps = ["//python/runfiles"], ) +sh_py_run_test( + name = "run_binary_with_runfiles_test", + build_python_zip = "no", + py_src = "bin_with_runfiles_test.py", + sh_src = "run_binary_with_runfiles_test.sh", + deps = ["//python/runfiles"], +) + build_test( name = "publishing", targets = [ diff --git a/tests/runfiles/bin_with_runfiles_test.py b/tests/runfiles/bin_with_runfiles_test.py new file mode 100644 index 0000000000..0fc85c92c0 --- /dev/null +++ b/tests/runfiles/bin_with_runfiles_test.py @@ -0,0 +1,32 @@ +# 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 unittest + +from python.runfiles import runfiles + + +class RunfilesTest(unittest.TestCase): + """Unit tests for `rules_python.python.runfiles.Runfiles`.""" + + def testCreatesDirectoryBasedRunfiles(self) -> None: + r = runfiles.Create() + repo = r.CurrentRepository() or "_main" + bin_location = r.Rlocation(os.path.join(repo,"tests/runfiles/bin_with_runfiles_test.py")) + self.maxDiff = None + self.assertEqual(bin_location, __file__) + +if __name__ == "__main__": + unittest.main() diff --git a/tests/runfiles/run_binary_with_runfiles_test.sh b/tests/runfiles/run_binary_with_runfiles_test.sh new file mode 100755 index 0000000000..e76c25de30 --- /dev/null +++ b/tests/runfiles/run_binary_with_runfiles_test.sh @@ -0,0 +1,39 @@ +# Copyright 2024 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. + +# --- begin runfiles.bash initialization v3 --- +# Copy-pasted from the Bazel Bash runfiles library v3. +set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash +source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \ + source "$0.runfiles/$f" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ + { echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e +# --- end runfiles.bash initialization v3 --- +set +e + +bin=$(rlocation $BIN_RLOCATION) +if [[ -z "$bin" ]]; then + echo "Unable to locate test binary: $BIN_RLOCATION" + exit 1 +fi + +# Test invocation without RUNFILES environment variables set +unset RUNFILES_MANIFEST_FILE +unset RUNFILES_DIR + +# Fail if tests fail +set -e +${bin} diff --git a/tests/runfiles/runfiles_test.py b/tests/runfiles/runfiles_test.py index a3837ac842..c6ad98a1b3 100644 --- a/tests/runfiles/runfiles_test.py +++ b/tests/runfiles/runfiles_test.py @@ -164,7 +164,7 @@ def _Run(): self.assertRaisesRegex(IOError, "non-existing path", _Run) - def testFailsToCreateAnyRunfilesBecauseEnvvarsAreNotDefined(self) -> None: + def testSucceedsWithoutEnvVars(self) -> None: with _MockFile(contents=["a b"]) as mf: runfiles.Create( { @@ -179,8 +179,8 @@ def testFailsToCreateAnyRunfilesBecauseEnvvarsAreNotDefined(self) -> None: "TEST_SRCDIR": "always ignored", } ) - self.assertIsNone(runfiles.Create({"TEST_SRCDIR": "always ignored"})) - self.assertIsNone(runfiles.Create({"FOO": "bar"})) + self.assertIsNotNone(runfiles.Create({"TEST_SRCDIR": "always ignored"})) + self.assertIsNotNone(runfiles.Create({"FOO": "bar"})) def testManifestBasedRlocation(self) -> None: with _MockFile(