Skip to content

Commit b5339a2

Browse files
committed
Add ignore_native_libs option for hermetic manifest generation
Fixes platform-specific inconsistency in gazelle_python_manifest where different native libraries (.so files) in platform-specific wheels caused different manifests on Linux vs macOS. Changes: - Add ignore_native_libs parameter to modules_mapping rule (default: False) - Skip .so file processing in generator.py when flag is enabled - Add comprehensive test coverage for the new functionality Usage: ```starlark modules_mapping( name = "modules_map", wheels = all_whl_requirements, ignore_native_libs = True, # Enables hermetic cross-platform builds ) ``` This solves the opencv-python-headless case and similar packages with platform-specific native libraries, enabling hermetic builds across different development platforms.
1 parent 05735c8 commit b5339a2

File tree

3 files changed

+39
-5
lines changed

3 files changed

+39
-5
lines changed

gazelle/modules_mapping/def.bzl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ def _modules_mapping_impl(ctx):
3838
args.set_param_file_format(format = "multiline")
3939
if ctx.attr.include_stub_packages:
4040
args.add("--include_stub_packages")
41+
if ctx.attr.ignore_native_libs:
42+
args.add("--ignore_native_libs")
4143
args.add("--output_file", modules_mapping)
4244
args.add_all("--exclude_patterns", ctx.attr.exclude_patterns)
4345
args.add_all("--wheels", all_wheels)
@@ -64,6 +66,11 @@ modules_mapping = rule(
6466
doc = "Whether to include stub packages in the mapping.",
6567
mandatory = False,
6668
),
69+
"ignore_native_libs": attr.bool(
70+
default = False,
71+
doc = "Whether to ignore native libraries (*.so files) for platform-independent mappings.",
72+
mandatory = False,
73+
),
6774
"modules_mapping_name": attr.string(
6875
default = "modules_mapping.json",
6976
doc = "The name for the output JSON file.",

gazelle/modules_mapping/generator.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ class Generator:
2626
output_file = None
2727
excluded_patterns = None
2828

29-
def __init__(self, stderr, output_file, excluded_patterns, include_stub_packages):
29+
def __init__(self, stderr, output_file, excluded_patterns, include_stub_packages, ignore_native_libs=False):
3030
self.stderr = stderr
3131
self.output_file = output_file
3232
self.excluded_patterns = [re.compile(pattern) for pattern in excluded_patterns]
3333
self.include_stub_packages = include_stub_packages
34+
self.ignore_native_libs = ignore_native_libs
3435
self.mapping = {}
3536

3637
# dig_wheel analyses the wheel .whl file determining the modules it provides
@@ -74,6 +75,10 @@ def simplify(self):
7475
def module_for_path(self, path, whl):
7576
ext = pathlib.Path(path).suffix
7677
if ext == ".py" or ext == ".so":
78+
# Skip native libraries if ignore_native_libs is enabled
79+
if ext == ".so" and self.ignore_native_libs:
80+
return
81+
7782
if "purelib" in path or "platlib" in path:
7883
root = "/".join(path.split("/")[2:])
7984
else:
@@ -158,10 +163,11 @@ def data_has_purelib_or_platlib(path):
158163
)
159164
parser.add_argument("--output_file", type=str)
160165
parser.add_argument("--include_stub_packages", action="store_true")
166+
parser.add_argument("--ignore_native_libs", action="store_true")
161167
parser.add_argument("--exclude_patterns", nargs="+", default=[])
162168
parser.add_argument("--wheels", nargs="+", default=[])
163169
args = parser.parse_args()
164170
generator = Generator(
165-
sys.stderr, args.output_file, args.exclude_patterns, args.include_stub_packages
171+
sys.stderr, args.output_file, args.exclude_patterns, args.include_stub_packages, args.ignore_native_libs
166172
)
167173
sys.exit(generator.run(args.wheels))

gazelle/modules_mapping/test_generator.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
class GeneratorTest(unittest.TestCase):
88
def test_generator(self):
99
whl = pathlib.Path(__file__).parent / "pytest-8.3.3-py3-none-any.whl"
10-
gen = Generator(None, None, {}, False)
10+
gen = Generator(None, None, {}, False, False)
1111
gen.dig_wheel(whl)
1212
self.assertLessEqual(
1313
{
@@ -21,7 +21,7 @@ def test_generator(self):
2121

2222
def test_stub_generator(self):
2323
whl = pathlib.Path(__file__).parent / "django_types-0.19.1-py3-none-any.whl"
24-
gen = Generator(None, None, {}, True)
24+
gen = Generator(None, None, {}, True, False)
2525
gen.dig_wheel(whl)
2626
self.assertLessEqual(
2727
{
@@ -32,13 +32,34 @@ def test_stub_generator(self):
3232

3333
def test_stub_excluded(self):
3434
whl = pathlib.Path(__file__).parent / "django_types-0.19.1-py3-none-any.whl"
35-
gen = Generator(None, None, {}, False)
35+
gen = Generator(None, None, {}, False, False)
3636
gen.dig_wheel(whl)
3737
self.assertEqual(
3838
{}.items(),
3939
gen.mapping.items(),
4040
)
4141

42+
def test_ignore_native_libs(self):
43+
# Test the ignore_native_libs functionality with the module_for_path method
44+
gen_with_native_libs = Generator(None, None, {}, False, False)
45+
gen_without_native_libs = Generator(None, None, {}, False, True)
46+
47+
# Simulate a Python file - should be included in both cases
48+
gen_with_native_libs.module_for_path("cv2/__init__.py", "opencv_python_headless-4.8.1-cp310-cp310-linux_x86_64.whl")
49+
gen_without_native_libs.module_for_path("cv2/__init__.py", "opencv_python_headless-4.8.1-cp310-cp310-linux_x86_64.whl")
50+
51+
# Simulate a native library - should be included only when ignore_native_libs=False
52+
gen_with_native_libs.module_for_path("opencv_python_headless.libs/libopenblas-r0-f650aae0.so", "opencv_python_headless-4.8.1-cp310-cp310-linux_x86_64.whl")
53+
gen_without_native_libs.module_for_path("opencv_python_headless.libs/libopenblas-r0-f650aae0.so", "opencv_python_headless-4.8.1-cp310-cp310-linux_x86_64.whl")
54+
55+
# Both should have the Python module mapping
56+
self.assertIn("cv2", gen_with_native_libs.mapping)
57+
self.assertIn("cv2", gen_without_native_libs.mapping)
58+
59+
# Only gen_with_native_libs should have the native library mapping
60+
self.assertIn("opencv_python_headless.libs.libopenblas-r0-f650aae0", gen_with_native_libs.mapping)
61+
self.assertNotIn("opencv_python_headless.libs.libopenblas-r0-f650aae0", gen_without_native_libs.mapping)
62+
4263

4364
if __name__ == "__main__":
4465
unittest.main()

0 commit comments

Comments
 (0)