blob: aad85b8a22027f79233b885b3acd074b615a5021 [file]
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
"""Reject build-only files, repeated native libraries and ELF debug sections."""
import argparse
import hashlib
from pathlib import Path
import struct
from zipfile import ZipFile
def elf_debug_sections(data):
if data[:4] != b"\x7fELF":
return []
if data[4] != 2:
raise ValueError("Expected a 64-bit ELF for the release wheel matrix")
endian = "<" if data[5] == 1 else ">"
offset = struct.unpack_from(endian + "Q", data, 40)[0]
size, count, names_index = struct.unpack_from(endian + "HHH", data, 58)
sections = [
struct.unpack_from(endian + "IIQQQQIIQQ", data, offset + i * size)
for i in range(count)
]
names_header = sections[names_index]
names = data[names_header[4] : names_header[4] + names_header[5]]
result = []
for section in sections:
name = names[section[0] :].split(b"\0", 1)[0].decode("ascii")
if name.startswith((".debug_", ".zdebug_")):
result.append(name)
return result
def check_wheel(path):
with ZipFile(path) as wheel:
files = [info for info in wheel.infolist() if not info.is_dir()]
names = [info.filename for info in files]
if len(names) != len(set(names)):
raise ValueError(f"Duplicate ZIP entries in {path}")
forbidden = [
name
for name in names
if name.startswith("tsfile/include/")
or Path(name).suffix.lower()
in {
".h",
".hpp",
".pxd",
".pxi",
".pyx",
".a",
".lib",
".pdb",
".cc",
".cpp",
}
]
if forbidden:
raise ValueError(f"Build-only files in {path}: {forbidden[:8]}")
native = [
name
for name in names
if Path(name).name.startswith("libtsfile")
or Path(name).name == "tsfile.dll"
]
if len(native) != 1:
raise ValueError(f"Expected one TsFile runtime library in {path}: {native}")
hashes = {}
for name in names:
if not (name.endswith((".so", ".dylib", ".dll", ".pyd")) or ".so." in name):
continue
data = wheel.read(name)
debug = elf_debug_sections(data)
if debug:
raise ValueError(f"ELF debug sections in {path}/{name}: {debug}")
digest = hashlib.sha256(data).hexdigest()
if digest in hashes:
raise ValueError(f"Repeated binary in {path}: {hashes[digest]}, {name}")
hashes[digest] = name
raw = sum(info.file_size for info in files)
print(
f"PASS: {Path(path).name}: {Path(path).stat().st_size} bytes compressed, "
f"{raw} bytes installed; one runtime library, no build-only files or ELF debug sections"
)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--wheelhouse", type=Path)
parser.add_argument("wheels", type=Path, nargs="*")
arguments = parser.parse_args()
wheels = arguments.wheels
if arguments.wheelhouse:
wheels += sorted(arguments.wheelhouse.glob("*.whl"))
if not wheels:
raise ValueError("No wheels found for content validation")
for wheel in wheels:
check_wheel(wheel)
if __name__ == "__main__":
main()