From 20143dbff307017c53e6c9326407a7ca3c577fb7 Mon Sep 17 00:00:00 2001 From: UebelAndre Date: Wed, 27 Dec 2023 07:49:40 -0800 Subject: [PATCH 1/6] feat(runfiles): Add static methods to `Runfiles` class to simplify interface --- python/runfiles/BUILD.bazel | 2 +- python/runfiles/README.md | 64 ++++++++++++++++++++++++++ python/runfiles/README.rst | 56 ----------------------- python/runfiles/runfiles.py | 90 ++++++++++++++++++++++--------------- 4 files changed, 119 insertions(+), 93 deletions(-) create mode 100644 python/runfiles/README.md delete mode 100644 python/runfiles/README.rst diff --git a/python/runfiles/BUILD.bazel b/python/runfiles/BUILD.bazel index 55c25c870d..dde5b45219 100644 --- a/python/runfiles/BUILD.bazel +++ b/python/runfiles/BUILD.bazel @@ -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", diff --git a/python/runfiles/README.md b/python/runfiles/README.md new file mode 100644 index 0000000000..9f6e7a3c92 --- /dev/null +++ b/python/runfiles/README.md @@ -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, + # ... + ) + ``` diff --git a/python/runfiles/README.rst b/python/runfiles/README.rst deleted file mode 100644 index ac61d2dd80..0000000000 --- a/python/runfiles/README.rst +++ /dev/null @@ -1,56 +0,0 @@ -bazel-runfiles library -====================== - -This is a Bazel Runfiles lookup library for Bazel-built Python binaries and tests. - -Learn about runfiles: read `Runfiles guide `_ -or watch `Fabian's BazelCon talk `_. - -Typical Usage -------------- - -1. Add the 'bazel-runfiles' dependency along with other third-party dependencies, for example in your - ``requirements.txt`` file. - -2. Depend on this runfiles library from your build rule, like you would other third-party libraries:: - - py_binary( - name = "my_binary", - ... - deps = [requirement("bazel-runfiles")], - ) - -3. Import the runfiles library:: - - import runfiles # not "from runfiles import runfiles" - -4. Create a Runfiles object and use rlocation to look up runfile paths:: - - 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 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, and the subprocess can't automatically - find the correct runfiles directory, you can explicitly set the right - environment variables for them:: - - import subprocess - import runfiles - - r = runfiles.Create() - env = {} - ... - env.update(r.EnvVars()) - p = subprocess.Popen([r.Rlocation("path/to/binary")], env, ...) \ No newline at end of file diff --git a/python/runfiles/runfiles.py b/python/runfiles/runfiles.py index 22409792f5..ffa2473f5c 100644 --- a/python/runfiles/runfiles.py +++ b/python/runfiles/runfiles.py @@ -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 @@ -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 @@ -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) From 61933c7d1826498a55a6ca0b5244db8bb6ed8d96 Mon Sep 17 00:00:00 2001 From: UebelAndre Date: Fri, 5 Jan 2024 08:30:21 -0800 Subject: [PATCH 2/6] updated README --- python/runfiles/README.md | 84 +++++++++++++++++++++++++-------------- 1 file changed, 55 insertions(+), 29 deletions(-) diff --git a/python/runfiles/README.md b/python/runfiles/README.md index 9f6e7a3c92..d2d3895c08 100644 --- a/python/runfiles/README.md +++ b/python/runfiles/README.md @@ -5,7 +5,11 @@ This is a Bazel Runfiles lookup library for Bazel-built Python binaries and test 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 +## Importing + +The Runfiles API is available from two sources, a direct Bazel target, and a [pypi](https://pypi.org/) package. + +## Pure Bazel imports 1. Depend on this runfiles library from your build rule, like you would other third-party libraries: @@ -23,42 +27,64 @@ or watch [Fabian's BazelCon talk](https://www.youtube.com/watch?v=5NbgUMH1OGo). from rules_python.python.runfiles import Runfiles ``` -3. Create a `Runfiles` object and use `Rlocation` to look up runfile paths: +## Pypi imports +1. Add the 'bazel-runfiles' dependency along with other third-party dependencies, for example in your `requirements.txt` file. + +2. Depend on this runfiles library from your build rule, like you would other third-party libraries: ```python - r = Runfiles.Create() - # ... - with open(r.Rlocation("my_workspace/path/to/my/data.txt"), "r") as f: - contents = f.readlines() - # ... + load("@pip_deps//:requirements.bzl", "requirement") + + py_binary( + name = "my_binary", + ... + deps = [requirement("bazel-runfiles")], + ) ``` - The code above creates a manifest- or directory-based implementation based on the environment variables in `os.environ`. See `Runfiles.Create()` for more info. +3. Import the runfiles library: + ```python + from runfiles import Runfiles + ``` - If you want to explicitly create a manifest- or directory-based - implementation, you can do so as follows: +## Typical Usage - ```python - r1 = Runfiles.CreateManifestBased("path/to/foo.runfiles_manifest") +Create a `Runfiles` object and use `Rlocation` to look up runfile paths: - r2 = Runfiles.CreateDirectoryBased("path/to/foo.runfiles/") - ``` +```python +r = Runfiles.Create() +# ... +with open(r.Rlocation("my_workspace/path/to/my/data.txt"), "r") as f: + contents = f.readlines() + # ... +``` - 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: +The code above creates a manifest- or directory-based implementation based on the environment variables in `os.environ`. See `Runfiles.Create()` for more info. - ```python - import subprocess - from rules_python.python.runfiles import Runfiles +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/") +``` - r = Runfiles.Create() - env = {} +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, # ... - env.update(r.EnvVars()) - p = subprocess.run( - [r.Rlocation("path/to/binary")], - env=env, - # ... - ) - ``` +) +``` From d75251bc867e9f807e01717a42ee56be0f6fbd67 Mon Sep 17 00:00:00 2001 From: UebelAndre Date: Fri, 5 Jan 2024 18:58:32 -0800 Subject: [PATCH 3/6] bzlmod --- python/runfiles/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/runfiles/README.md b/python/runfiles/README.md index d2d3895c08..5620c4a299 100644 --- a/python/runfiles/README.md +++ b/python/runfiles/README.md @@ -24,7 +24,7 @@ The Runfiles API is available from two sources, a direct Bazel target, and a [py 2. Import the runfiles library: ```python - from rules_python.python.runfiles import Runfiles + from python.runfiles import Runfiles ``` ## Pypi imports From 985d30b0fe8ab6145e420d986c4004ebbb4ee709 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Tue, 9 Jan 2024 09:20:37 +0900 Subject: [PATCH 4/6] Update import path in the README --- python/runfiles/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/runfiles/README.md b/python/runfiles/README.md index 5620c4a299..2a57c76846 100644 --- a/python/runfiles/README.md +++ b/python/runfiles/README.md @@ -76,7 +76,7 @@ environment variables for them: ```python import subprocess -from rules_python.python.runfiles import Runfiles +from python.runfiles import Runfiles r = Runfiles.Create() env = {} From 8ad814bfd68cf80496df020ab1a06856a1e06f83 Mon Sep 17 00:00:00 2001 From: UebelAndre Date: Mon, 8 Jan 2024 16:45:16 -0800 Subject: [PATCH 5/6] changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af61b44cce..c4106d1930 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,10 @@ A brief description of the categories of changes: [0.XX.0]: https://github.com/bazelbuild/rules_python/releases/tag/0.XX.0 +* (runfiles) `rules_python.python.runfiles.Runfiles` now has a static `Create` + method to make imports more ergonomic. Users should only need to import the + `Runfiles` object to locate runfiles. + ### Changed ### Fixed From 7bb8d52061e7a6a6b6ece657d66a4e69b1519a93 Mon Sep 17 00:00:00 2001 From: Ignas Anikevicius <240938+aignas@users.noreply.github.com> Date: Wed, 10 Jan 2024 08:58:06 +0900 Subject: [PATCH 6/6] chore: move the CHANGELOG snippet to the 'Added' section --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 072cf28efd..5741d02870 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,10 +21,6 @@ A brief description of the categories of changes: [0.XX.0]: https://github.com/bazelbuild/rules_python/releases/tag/0.XX.0 -* (runfiles) `rules_python.python.runfiles.Runfiles` now has a static `Create` - method to make imports more ergonomic. Users should only need to import the - `Runfiles` object to locate runfiles. - ### Changed ### Fixed @@ -44,6 +40,10 @@ A brief description of the categories of changes: Python. This means that `WORKSPACE` users can now copy the `requirements.bzl` file for vendoring as seen in the updated `pip_parse_vendored` example. +* (runfiles) `rules_python.python.runfiles.Runfiles` now has a static `Create` + method to make imports more ergonomic. Users should only need to import the + `Runfiles` object to locate runfiles. + ## [0.28.0] - 2024-01-07 [0.28.0]: https://github.com/bazelbuild/rules_python/releases/tag/0.28.0