Skip to content

Commit bb04a49

Browse files
authored
Make spread_purelib_into_root behave like the wheel was installed by (bazel-contrib#581)
pip. Merge purelib dir into toplevel even if purelib and toplevel have subdirs with the same name. See tensorflow-io for an example of a package which was not installed correctly by rules_python before this change.
1 parent 356407d commit bb04a49

File tree

3 files changed

+72
-7
lines changed

3 files changed

+72
-7
lines changed

python/pip_install/extract_wheels/lib/BUILD

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,17 @@ py_test(
9494
],
9595
)
9696

97+
py_test(
98+
name = "purelib_test",
99+
size = "small",
100+
srcs = [
101+
"purelib_test.py",
102+
],
103+
deps = [
104+
":lib",
105+
],
106+
)
107+
97108
filegroup(
98109
name = "distribution",
99110
srcs = glob(

python/pip_install/extract_wheels/lib/purelib.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Functions to make purelibs Bazel compatible"""
2+
import os
23
import pathlib
34
import shutil
45

@@ -34,20 +35,33 @@ def spread_purelib_into_root(wheel_dir: str) -> None:
3435
_spread_purelib(child, wheel_dir)
3536

3637

38+
def backport_copytree(src: pathlib.Path, dst: pathlib.Path):
39+
"""Implementation similar to shutil.copytree.
40+
41+
shutil.copytree before python3.8 does not allow merging one tree with
42+
an existing one. This function does that, while ignoring complications around symlinks, which
43+
can't exist is wheels (See https://bugs.python.org/issue27318).
44+
"""
45+
os.makedirs(dst, exist_ok=True)
46+
for path in src.iterdir():
47+
if path.is_dir():
48+
backport_copytree(path, pathlib.Path(dst, path.name))
49+
elif not pathlib.Path(dst, path.name).exists():
50+
shutil.copy(path, dst)
51+
52+
3753
def _spread_purelib(purelib_dir: pathlib.Path, root_dir: str) -> None:
3854
"""Recursively moves all sibling directories of the purelib to the root.
3955
4056
Args:
4157
purelib_dir: The directory of the purelib.
4258
root_dir: The directory to move files into.
4359
"""
44-
for grandchild in purelib_dir.iterdir():
45-
# Some purelib Wheels, like Tensorflow 2.0.0, have directories
46-
# split between the root and the purelib directory. In this case
47-
# we should leave the purelib 'sibling' alone.
48-
# See: https://github.com/dillon-giacoppo/rules_python_external/issues/8
49-
if not pathlib.Path(root_dir, grandchild.name).exists():
50-
shutil.move(
60+
for child in purelib_dir.iterdir():
61+
if child.is_dir():
62+
backport_copytree(src=child, dst=pathlib.Path(root_dir, child.name))
63+
elif not pathlib.Path(root_dir, grandchild.name).exists():
64+
shutil.copy(
5165
src=str(grandchild),
5266
dst=root_dir,
5367
)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import os
2+
import unittest
3+
from contextlib import contextmanager
4+
from pathlib import Path
5+
from tempfile import TemporaryDirectory
6+
7+
from python.pip_install.extract_wheels.lib import purelib
8+
9+
10+
class TestPurelibTestCase(unittest.TestCase):
11+
@contextmanager
12+
def setup_faux_unzipped_wheel(self):
13+
files = [
14+
("faux_wheel.data/purelib/toplevel/foo.py", "# foo"),
15+
("faux_wheel.data/purelib/toplevel/dont_overwrite.py", "overwritten"),
16+
("faux_wheel.data/purelib/toplevel/subdir/baz.py", "overwritten"),
17+
("toplevel/bar.py", "# bar"),
18+
("toplevel/dont_overwrite.py", "original"),
19+
]
20+
with TemporaryDirectory() as td:
21+
self.td_path = Path(td)
22+
self.purelib_path = self.td_path / Path("faux_wheel.data/purelib")
23+
for file_, content in files:
24+
path = self.td_path / Path(file_)
25+
path.parent.mkdir(parents=True, exist_ok=True)
26+
with open(str(path), "w") as f:
27+
f.write(content)
28+
yield
29+
30+
def test_spread_purelib_(self):
31+
with self.setup_faux_unzipped_wheel():
32+
purelib._spread_purelib(self.purelib_path, self.td_path)
33+
self.assertTrue(Path(self.td_path, "toplevel/foo.py").exists())
34+
self.assertTrue(Path(self.td_path, "toplevel/subdir/baz.py").exists())
35+
with open(Path(self.td_path, "toplevel/dont_overwrite.py")) as original:
36+
self.assertEqual(original.read().strip(), "original")
37+
38+
39+
if __name__ == "__main__":
40+
unittest.main()

0 commit comments

Comments
 (0)