diff --git a/docs/environment-variables.md b/docs/environment-variables.md index 9efa48c606..b818adef24 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -112,8 +112,8 @@ the root pycache directory: 2. `$HOME/.cache` (non-Windows), the XDG default when `XDG_CACHE_HOME` is unset, or `LOCALAPPDATA` (Windows). -If such a diretory cannot be found, or created, then `/dev/null` will be used, -which will effectively disable pyc caching. +If `/dev/null` (`NUL` on Windows) is specified, or if a directory cannot be +found or created, then pyc caching will be disabled. :::{versionchanged} VERSION_NEXT_PATCH Temporary directories (`TMP`, `TEMP`, `/tmp` and `C:\Temp`) are no longer used. diff --git a/python/private/python_repository.bzl b/python/private/python_repository.bzl index 8517a3fa5d..d5ec92f612 100644 --- a/python/private/python_repository.bzl +++ b/python/private/python_repository.bzl @@ -60,6 +60,10 @@ def _get_pycache_root(rctx): {type}`path | None` The path to the pycache root, or None if it couldn't be created. """ + res = rctx.getenv("RULES_PYTHON_PYCACHE_DIR") + if res and res.lower() in ("/dev/null", "nul"): + return None + os_name = repo_utils.get_platforms_os_name(rctx) is_windows = os_name == "windows" @@ -74,7 +78,6 @@ def _get_pycache_root(rctx): hash("{}:{}".format(rctx.workspace_root, runtime_hash)), ) - res = rctx.getenv("RULES_PYTHON_PYCACHE_DIR") if res: return repo_utils.mkdir(rctx, rctx.path(res).get_child(suffix)) @@ -100,7 +103,7 @@ def _get_pycache_root(rctx): return repo_utils.mkdir(rctx, path) def _create_pycache_symlinks(rctx, logger): - """Finds all directories with a .py file and creates __pycache__ symlinks. + """Finds all directories with a .py file and creates __pycache__ entries. Args: rctx: {type}`repository_ctx` The repository rule's context object. @@ -110,9 +113,6 @@ def _create_pycache_symlinks(rctx, logger): logger.info(lambda: "pycache root: {}".format(pycache_root)) pycache_root_str = str(pycache_root) if pycache_root else None - os_name = repo_utils.get_platforms_os_name(rctx) - null_device = "NUL" if os_name == "windows" else "/dev/null" - queue = [rctx.path(".")] # Starlark doesn't support recursion, use a loop with a queue. @@ -146,7 +146,13 @@ def _create_pycache_symlinks(rctx, logger): rctx.symlink(target_dir, pycache_dir) else: rctx.delete(pycache_dir) - rctx.symlink(null_device, pycache_dir) + + # The intent is to disable pyc generation, which we can't + # entirely do directly. Symlinking to /dev/null makes it point + # to a character device, which can act weird when read or put + # into a zip. A regular file seems to make python skip local pyc + # generation, so use that. + rctx.file(pycache_dir, content = "", executable = False) def _python_repository_impl(rctx): if rctx.attr.distutils and rctx.attr.distutils_content: diff --git a/tests/integration/custom_commands_test.py b/tests/integration/custom_commands_test.py index c186d57d5d..a707d0dd25 100644 --- a/tests/integration/custom_commands_test.py +++ b/tests/integration/custom_commands_test.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pathlib import unittest from tests.integration import runner @@ -30,6 +31,7 @@ def test_run_build_python_zip_false(self): # Regression test for https://github.com/bazel-contrib/rules_python/issues/4198 def test_runtime_files_exclude_pycache_symlinks(self): + output_base = pathlib.Path(self.run_bazel("info", "output_base").stdout.strip()) result = self.run_bazel( "cquery", "--repo_env=RULES_PYTHON_PYCACHE_DIR=/dev/null", @@ -41,6 +43,12 @@ def test_runtime_files_exclude_pycache_symlinks(self): pycache = [f for f in files if "__pycache__" in f] self.assertEqual(pycache, []) + os_py = next(f for f in files if f.endswith("/os.py") or f.endswith("\\os.py")) + stdlib_pycache = output_base / pathlib.Path(os_py).parent / "__pycache__" + self.assertFalse(stdlib_pycache.is_symlink()) + self.assertFalse(stdlib_pycache.is_dir()) + self.assertTrue(stdlib_pycache.is_file()) + if __name__ == "__main__": # Enabling this makes the runner log subprocesses as the test goes along.