Skip to content

Commit

Permalink
feat(runfiles): Add static methods to Runfiles class to simplify in…
Browse files Browse the repository at this point in the history
…terface
  • Loading branch information
UebelAndre committed Dec 27, 2023
1 parent 4c2d7d9 commit 8ac58a6
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 93 deletions.
2 changes: 1 addition & 1 deletion python/runfiles/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ py_wheel(
"Development Status :: 5 - Production/Stable",
"License :: OSI Approved :: Apache Software License",
],
description_file = "README.rst",
description_file = "README.md",
dist_folder = "dist",
distribution = "bazel_runfiles",
homepage = "https://github.com/bazelbuild/rules_python",
Expand Down
64 changes: 64 additions & 0 deletions python/runfiles/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# bazel-runfiles library

This is a Bazel Runfiles lookup library for Bazel-built Python binaries and tests.

Learn about runfiles: read [Runfiles guide](https://bazel.build/extending/rules#runfiles)
or watch [Fabian's BazelCon talk](https://www.youtube.com/watch?v=5NbgUMH1OGo).

## Typical Usage

1. Depend on this runfiles library from your build rule, like you would other third-party libraries:

```python
py_binary(
name = "my_binary",
...
deps = ["@rules_python//python/runfiles"],
)
```

2. Import the runfiles library:

```python
from rules_python.python.runfiles import Runfiles
```

3. Create a `Runfiles` object and use `Rlocation` to look up runfile paths:

```python
r = Runfiles.Create()
# ...
with open(r.Rlocation("my_workspace/path/to/my/data.txt"), "r") as f:
contents = f.readlines()
# ...
```

The code above creates a manifest- or directory-based implementation based on the environment variables in `os.environ`. See `Runfiles.Create()` for more info.

If you want to explicitly create a manifest- or directory-based
implementation, you can do so as follows:

```python
r1 = Runfiles.CreateManifestBased("path/to/foo.runfiles_manifest")

r2 = Runfiles.CreateDirectoryBased("path/to/foo.runfiles/")
```

If you want to start subprocesses, and the subprocess can't automatically
find the correct runfiles directory, you can explicitly set the right
environment variables for them:

```python
import subprocess
from rules_python.python.runfiles import Runfiles

r = Runfiles.Create()
env = {}
# ...
env.update(r.EnvVars())
p = subprocess.run(
[r.Rlocation("path/to/binary")],
env=env,
# ...
)
```
56 changes: 0 additions & 56 deletions python/runfiles/README.rst

This file was deleted.

90 changes: 54 additions & 36 deletions python/runfiles/runfiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

"""Runfiles lookup library for Bazel-built Python binaries and tests.
See @rules_python//python/runfiles/README.rst for usage instructions.
See @rules_python//python/runfiles/README.md for usage instructions.
"""
import inspect
import os
Expand Down Expand Up @@ -267,6 +267,56 @@ def CurrentRepository(self, frame: int = 1) -> str:
# canonical name.
return caller_runfiles_directory

# TODO: Update return type to Self when 3.11 is the min version
# https://peps.python.org/pep-0673/
@staticmethod
def CreateManifestBased(manifest_path: str) -> "Runfiles":
return Runfiles(_ManifestBased(manifest_path))

# TODO: Update return type to Self when 3.11 is the min version
# https://peps.python.org/pep-0673/
@staticmethod
def CreateDirectoryBased(runfiles_dir_path: str) -> "Runfiles":
return Runfiles(_DirectoryBased(runfiles_dir_path))

# TODO: Update return type to Self when 3.11 is the min version
# https://peps.python.org/pep-0673/
@staticmethod
def Create(env: Optional[Dict[str, str]] = None) -> Optional["Runfiles"]:
"""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" 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 directory:
return CreateDirectoryBased(directory)

return None


# Support legacy imports by defining a private symbol.
_Runfiles = Runfiles
Expand Down Expand Up @@ -309,44 +359,12 @@ def _ParseRepoMapping(repo_mapping_path: Optional[str]) -> Dict[Tuple[str, str],


def CreateManifestBased(manifest_path: str) -> Runfiles:
return Runfiles(_ManifestBased(manifest_path))
return Runfiles.CreateManifestBased(manifest_path)


def CreateDirectoryBased(runfiles_dir_path: str) -> Runfiles:
return Runfiles(_DirectoryBased(runfiles_dir_path))
return Runfiles.CreateDirectoryBased(runfiles_dir_path)


def Create(env: Optional[Dict[str, str]] = None) -> Optional[Runfiles]:
"""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" 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 directory:
return CreateDirectoryBased(directory)

return None
return Runfiles.Create(env)

0 comments on commit 8ac58a6

Please sign in to comment.