You need to sign in or sign up before continuing.
Unverified Commit 4db3dc6d authored by Edgar Andrés Margffoy Tuay's avatar Edgar Andrés Margffoy Tuay Committed by GitHub
Browse files

Fix wheel relocation issues (#2777)



* [DEBUG] Check wheel relocation issues

* Call delocate on Mac

* Use yum instead of conda

* Do not copy ffmpeg dylibs

* Linux dry run

* Do not download FFmpeg on Mac

* Install bzip2

* Restore FFmpeg on Windows

* Remove ffmpeg temporarily

* Do not remove dependencies

* Disable FFmpeg temporarily on Linux wheels

* Test relocation in Linux

* Add docstring

* Call relocation script

* Minor error correction

* Import auditwheel only on Linux

* Restore ffmpeg again on Windows

* Return *device

* Try fix Windows

* Fix clang-format

* Start windows patchwork

* Copy all DLLs

* Disable FFmpeg on Windows for now

* Bugfix
Co-authored-by: default avatarFrancisco Massa <fvsmassa@gmail.com>
parent ebe3f023
...@@ -17,19 +17,20 @@ if [[ "$(uname)" == Darwin || "$OSTYPE" == "msys" ]]; then ...@@ -17,19 +17,20 @@ if [[ "$(uname)" == Darwin || "$OSTYPE" == "msys" ]]; then
bin_path=$(dirname $python_exec) bin_path=$(dirname $python_exec)
env_path=$(dirname $bin_path) env_path=$(dirname $bin_path)
if [[ "$(uname)" == Darwin ]]; then if [[ "$(uname)" == Darwin ]]; then
# Include LibPNG # Install delocate to relocate the required binaries
cp "$env_path/lib/libpng16.dylib" torchvision pip_install delocate
# Include LibJPEG
cp "$env_path/lib/libjpeg.dylib" torchvision
else else
cp "$bin_path/Library/bin/libpng16.dll" torchvision cp "$bin_path/Library/bin/libpng16.dll" torchvision
cp "$bin_path/Library/bin/libjpeg.dll" torchvision cp "$bin_path/Library/bin/libjpeg.dll" torchvision
fi fi
else else
# Include LibPNG # Install auditwheel to get some inspection utilities
cp "/usr/lib64/libpng.so" torchvision pip_install auditwheel
# Include LibJPEG
cp "/usr/lib64/libjpeg.so" torchvision # Point to custom libraries
export LD_LIBRARY_PATH=$(pwd)/ext_libraries/lib:$LD_LIBRARY_PATH
export TORCHVISION_INCLUDE=$(pwd)/ext_libraries/include
export TORCHVISION_LIBRARY=$(pwd)/ext_libraries/lib
fi fi
download_copy_ffmpeg download_copy_ffmpeg
...@@ -39,3 +40,20 @@ if [[ "$OSTYPE" == "msys" ]]; then ...@@ -39,3 +40,20 @@ if [[ "$OSTYPE" == "msys" ]]; then
else else
IS_WHEEL=1 python setup.py bdist_wheel IS_WHEEL=1 python setup.py bdist_wheel
fi fi
if [[ "$(uname)" == Darwin ]]; then
pushd dist/
python_exec="$(which python)"
bin_path=$(dirname $python_exec)
env_path=$(dirname $bin_path)
for whl in *.whl; do
DYLD_LIBRARY_PATH="$env_path/lib/:$DYLD_LIBRARY_PATH" delocate-wheel -v $whl
done
else
if [[ "$OSTYPE" == "msys" ]]; then
"$script_dir/windows/internal/vc_env_helper.bat" python $script_dir/wheel/relocate.py
else
LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH" python $script_dir/wheel/relocate.py
fi
fi
...@@ -184,8 +184,8 @@ setup_wheel_python() { ...@@ -184,8 +184,8 @@ setup_wheel_python() {
# Install libpng from Anaconda (defaults) # Install libpng from Anaconda (defaults)
conda install libpng jpeg -y conda install libpng jpeg -y
else else
# Install native CentOS libPNG # Install native CentOS libJPEG, LAME, freetype and GnuTLS
yum install -y libpng-devel libjpeg-turbo-devel yum install -y libjpeg-turbo-devel lame freetype gnutls
case "$PYTHON_VERSION" in case "$PYTHON_VERSION" in
2.7) 2.7)
if [[ -n "$UNICODE_ABI" ]]; then if [[ -n "$UNICODE_ABI" ]]; then
...@@ -203,7 +203,13 @@ setup_wheel_python() { ...@@ -203,7 +203,13 @@ setup_wheel_python() {
exit 1 exit 1
;; ;;
esac esac
export PATH="/opt/python/$python_abi/bin:$PATH" # Download all the dependencies required to compile image and video_reader
# extensions
mkdir -p ext_libraries
pushd ext_libraries
popd
export PATH="/opt/python/$python_abi/bin:$(pwd)/ext_libraries/bin:$PATH"
fi fi
} }
...@@ -353,10 +359,8 @@ setup_junit_results_folder() { ...@@ -353,10 +359,8 @@ setup_junit_results_folder() {
download_copy_ffmpeg() { download_copy_ffmpeg() {
mkdir ffmpeg_tmp
cd ffmpeg_tmp
if [[ "$OSTYPE" == "msys" ]]; then if [[ "$OSTYPE" == "msys" ]]; then
# conda install -yq ffmpeg -c pytorch # conda install -yq ffmpeg=4.2 -c pytorch
# curl -L -q https://anaconda.org/pytorch/ffmpeg/4.3/download/win-64/ffmpeg-4.3-ha925a31_0.tar.bz2 --output ffmpeg-4.3-ha925a31_0.tar.bz2 # curl -L -q https://anaconda.org/pytorch/ffmpeg/4.3/download/win-64/ffmpeg-4.3-ha925a31_0.tar.bz2 --output ffmpeg-4.3-ha925a31_0.tar.bz2
# bzip2 --decompress --stdout ffmpeg-4.3-ha925a31_0.tar.bz2 | tar -x --file=- # bzip2 --decompress --stdout ffmpeg-4.3-ha925a31_0.tar.bz2 | tar -x --file=-
# cp Library/bin/*.dll ../torchvision # cp Library/bin/*.dll ../torchvision
...@@ -365,24 +369,15 @@ download_copy_ffmpeg() { ...@@ -365,24 +369,15 @@ download_copy_ffmpeg() {
if [[ "$(uname)" == Darwin ]]; then if [[ "$(uname)" == Darwin ]]; then
conda install -yq ffmpeg=4.2 -c pytorch conda install -yq ffmpeg=4.2 -c pytorch
conda install -yq wget conda install -yq wget
wget -q https://anaconda.org/pytorch/ffmpeg/4.2/download/osx-64/ffmpeg-4.2-h0a44026_0.tar.bz2
tar -xjvf ffmpeg-4.2-h0a44026_0.tar.bz2
for f in lib/*.dylib; do
if [[ $f =~ ([a-z])+\.dylib ]]; then
cp $f ../torchvision
fi
done
else else
wget -q https://anaconda.org/pytorch/ffmpeg/4.2/download/linux-64/ffmpeg-4.2-hf484d3e_0.tar.bz2 # pushd ext_libraries
tar -xjvf ffmpeg-4.2-hf484d3e_0.tar.bz2 # wget -q https://anaconda.org/pytorch/ffmpeg/4.2/download/linux-64/ffmpeg-4.2-hf484d3e_0.tar.bz2
cp lib/*.so ../torchvision # tar -xjvf ffmpeg-4.2-hf484d3e_0.tar.bz2
cp -r lib/* /usr/lib # rm -rf ffmpeg-4.2-hf484d3e_0.tar.bz2
cp -r bin/* /usr/bin # ldconfig
cp -r include/* /usr/include # which ffmpeg
ldconfig # popd
which ffmpeg echo "FFmpeg is disabled currently on Linux"
fi fi
fi fi
cd ..
rm -rf ffmpeg_tmp
} }
# -*- coding: utf-8 -*-
"""Helper script to package wheels and relocate binaries."""
# Standard library imports
import os
import io
import sys
import glob
import shutil
import zipfile
import hashlib
import platform
import subprocess
import os.path as osp
from base64 import urlsafe_b64encode
# Third party imports
if sys.platform == 'linux':
from auditwheel.lddtree import lddtree
from wheel.bdist_wheel import get_abi_tag
WHITELIST = {
'libgcc_s.so.1', 'libstdc++.so.6', 'libm.so.6',
'libdl.so.2', 'librt.so.1', 'libc.so.6',
'libnsl.so.1', 'libutil.so.1', 'libpthread.so.0',
'libresolv.so.2', 'libX11.so.6', 'libXext.so.6',
'libXrender.so.1', 'libICE.so.6', 'libSM.so.6',
'libGL.so.1', 'libgobject-2.0.so.0', 'libgthread-2.0.so.0',
'libglib-2.0.so.0', 'ld-linux-x86-64.so.2', 'ld-2.17.so'
}
WINDOWS_WHITELIST = {
'MSVCP140.dll', 'KERNEL32.dll',
'VCRUNTIME140_1.dll', 'VCRUNTIME140.dll',
'api-ms-win-crt-heap-l1-1-0.dll',
'api-ms-win-crt-runtime-l1-1-0.dll',
'api-ms-win-crt-stdio-l1-1-0.dll',
'api-ms-win-crt-filesystem-l1-1-0.dll',
'api-ms-win-crt-string-l1-1-0.dll',
'api-ms-win-crt-environment-l1-1-0.dll',
'api-ms-win-crt-math-l1-1-0.dll',
'api-ms-win-crt-convert-l1-1-0.dll'
}
HERE = osp.dirname(osp.abspath(__file__))
PACKAGE_ROOT = osp.dirname(osp.dirname(HERE))
PLATFORM_ARCH = platform.machine()
PYTHON_VERSION = sys.version_info
def read_chunks(file, size=io.DEFAULT_BUFFER_SIZE):
"""Yield pieces of data from a file-like object until EOF."""
while True:
chunk = file.read(size)
if not chunk:
break
yield chunk
def rehash(path, blocksize=1 << 20):
"""Return (hash, length) for path using hashlib.sha256()"""
h = hashlib.sha256()
length = 0
with open(path, 'rb') as f:
for block in read_chunks(f, size=blocksize):
length += len(block)
h.update(block)
digest = 'sha256=' + urlsafe_b64encode(
h.digest()
).decode('latin1').rstrip('=')
# unicode/str python2 issues
return (digest, str(length)) # type: ignore
def unzip_file(file, dest):
"""Decompress zip `file` into directory `dest`."""
with zipfile.ZipFile(file, 'r') as zip_ref:
zip_ref.extractall(dest)
def is_program_installed(basename):
"""
Return program absolute path if installed in PATH.
Otherwise, return None
On macOS systems, a .app is considered installed if
it exists.
"""
if (sys.platform == 'darwin' and basename.endswith('.app') and
osp.exists(basename)):
return basename
for path in os.environ["PATH"].split(os.pathsep):
abspath = osp.join(path, basename)
if osp.isfile(abspath):
return abspath
def find_program(basename):
"""
Find program in PATH and return absolute path
Try adding .exe or .bat to basename on Windows platforms
(return None if not found)
"""
names = [basename]
if os.name == 'nt':
# Windows platforms
extensions = ('.exe', '.bat', '.cmd', '.dll')
if not basename.endswith(extensions):
names = [basename + ext for ext in extensions] + [basename]
for name in names:
path = is_program_installed(name)
if path:
return path
def patch_new_path(library_path, new_dir):
library = osp.basename(library_path)
name, *rest = library.split('.')
rest = '.'.join(rest)
hash_id = hashlib.sha256(library_path.encode('utf-8')).hexdigest()[:8]
new_name = '.'.join([name, hash_id, rest])
return osp.join(new_dir, new_name)
def find_dll_dependencies(dumpbin, binary):
out = subprocess.run([dumpbin, "/dependents", binary],
stdout=subprocess.PIPE)
out = out.stdout.strip().decode('utf-8')
start_index = out.find('dependencies:') + len('dependencies:')
end_index = out.find('Summary')
dlls = out[start_index:end_index].strip()
dlls = dlls.split(os.linesep)
dlls = [dll.strip() for dll in dlls]
return dlls
def relocate_elf_library(patchelf, output_dir, output_library, binary):
"""
Relocate an ELF shared library to be packaged on a wheel.
Given a shared library, find the transitive closure of its dependencies,
rename and copy them into the wheel while updating their respective rpaths.
"""
print('Relocating {0}'.format(binary))
binary_path = osp.join(output_library, binary)
ld_tree = lddtree(binary_path)
tree_libs = ld_tree['libs']
binary_queue = [(n, binary) for n in ld_tree['needed']]
binary_paths = {binary: binary_path}
binary_dependencies = {}
while binary_queue != []:
library, parent = binary_queue.pop(0)
library_info = tree_libs[library]
print(library)
if library_info['path'] is None:
print('Omitting {0}'.format(library))
continue
if library in WHITELIST:
# Omit glibc/gcc/system libraries
print('Omitting {0}'.format(library))
continue
parent_dependencies = binary_dependencies.get(parent, [])
parent_dependencies.append(library)
binary_dependencies[parent] = parent_dependencies
if library in binary_paths:
continue
binary_paths[library] = library_info['path']
binary_queue += [(n, library) for n in library_info['needed']]
print('Copying dependencies to wheel directory')
new_libraries_path = osp.join(output_dir, 'torchvision.libs')
os.makedirs(new_libraries_path)
new_names = {binary: binary_path}
for library in binary_paths:
if library != binary:
library_path = binary_paths[library]
new_library_path = patch_new_path(library_path, new_libraries_path)
print('{0} -> {1}'.format(library, new_library_path))
shutil.copyfile(library_path, new_library_path)
new_names[library] = new_library_path
print('Updating dependency names by new files')
for library in binary_paths:
if library != binary:
if library not in binary_dependencies:
continue
library_dependencies = binary_dependencies[library]
new_library_name = new_names[library]
for dep in library_dependencies:
new_dep = osp.basename(new_names[dep])
print('{0}: {1} -> {2}'.format(library, dep, new_dep))
subprocess.check_output(
[
patchelf,
'--replace-needed',
dep,
new_dep,
new_library_name
],
cwd=new_libraries_path)
print('Updating library rpath')
subprocess.check_output(
[
patchelf,
'--set-rpath',
"$ORIGIN",
new_library_name
],
cwd=new_libraries_path)
subprocess.check_output(
[
patchelf,
'--print-rpath',
new_library_name
],
cwd=new_libraries_path)
print("Update library dependencies")
library_dependencies = binary_dependencies[binary]
for dep in library_dependencies:
new_dep = osp.basename(new_names[dep])
print('{0}: {1} -> {2}'.format(binary, dep, new_dep))
subprocess.check_output(
[
patchelf,
'--replace-needed',
dep,
new_dep,
binary
],
cwd=output_library)
print('Update library rpath')
subprocess.check_output(
[
patchelf,
'--set-rpath',
"$ORIGIN:$ORIGIN/../torchvision.libs",
binary_path
],
cwd=output_library
)
def relocate_dll_library(dumpbin, output_dir, output_library, binary):
print('Relocating {0}'.format(binary))
binary_path = osp.join(output_library, binary)
library_dlls = find_dll_dependencies(dumpbin, binary_path)
binary_queue = [(dll, binary) for dll in library_dlls]
binary_paths = {binary: binary_path}
binary_dependencies = {}
while binary_queue != []:
library, parent = binary_queue.pop(0)
if library in WINDOWS_WHITELIST or library.startswith('api-ms-win'):
print('Omitting {0}'.format(library))
continue
library_path = find_program(library)
if library_path is None:
print('{0} not found'.format(library))
continue
if osp.basename(osp.dirname(library_path)) == 'system32':
continue
print('{0}: {1}'.format(library, library_path))
parent_dependencies = binary_dependencies.get(parent, [])
parent_dependencies.append(library)
binary_dependencies[parent] = parent_dependencies
if library in binary_paths:
continue
binary_paths[library] = library_path
downstream_dlls = find_dll_dependencies(dumpbin, library_path)
binary_queue += [(n, library) for n in downstream_dlls]
print('Copying dependencies to wheel directory')
package_dir = osp.join(output_dir, 'torchvision')
for library in binary_paths:
if library != binary:
library_path = binary_paths[library]
new_library_path = osp.join(package_dir, library)
print('{0} -> {1}'.format(library, new_library_path))
shutil.copyfile(library_path, new_library_path)
def compress_wheel(output_dir, wheel, wheel_dir, wheel_name):
"""Create RECORD file and compress wheel distribution."""
print('Update RECORD file in wheel')
dist_info = glob.glob(osp.join(output_dir, '*.dist-info'))[0]
record_file = osp.join(dist_info, 'RECORD')
with open(record_file, 'w') as f:
for root, _, files in os.walk(output_dir):
for this_file in files:
full_file = osp.join(root, this_file)
rel_file = osp.relpath(full_file, output_dir)
if full_file == record_file:
f.write('{0},,\n'.format(rel_file))
else:
digest, size = rehash(full_file)
f.write('{0},{1},{2}\n'.format(rel_file, digest, size))
print('Compressing wheel')
base_wheel_name = osp.join(wheel_dir, wheel_name)
shutil.make_archive(base_wheel_name, 'zip', output_dir)
os.remove(wheel)
shutil.move('{0}.zip'.format(base_wheel_name), wheel)
shutil.rmtree(output_dir)
def patch_linux():
# Get patchelf location
patchelf = find_program('patchelf')
if patchelf is None:
raise FileNotFoundError('Patchelf was not found in the system, please'
' make sure that is available on the PATH.')
# Find wheel
print('Finding wheels...')
wheels = glob.glob(osp.join(PACKAGE_ROOT, 'dist', '*.whl'))
output_dir = osp.join(PACKAGE_ROOT, 'dist', '.wheel-process')
image_binary = 'image.so'
video_binary = 'video_reader.so'
torchvision_binaries = [image_binary, video_binary]
for wheel in wheels:
if osp.exists(output_dir):
shutil.rmtree(output_dir)
os.makedirs(output_dir)
print('Unzipping wheel...')
wheel_file = osp.basename(wheel)
wheel_dir = osp.dirname(wheel)
print('{0}'.format(wheel_file))
wheel_name, _ = osp.splitext(wheel_file)
unzip_file(wheel, output_dir)
print('Finding ELF dependencies...')
output_library = osp.join(output_dir, 'torchvision')
for binary in torchvision_binaries:
if osp.exists(osp.join(output_library, binary)):
relocate_elf_library(
patchelf, output_dir, output_library, binary)
compress_wheel(output_dir, wheel, wheel_dir, wheel_name)
def patch_win():
# Get dumpbin location
dumpbin = find_program('dumpbin')
if dumpbin is None:
raise FileNotFoundError('Dumpbin was not found in the system, please'
' make sure that is available on the PATH.')
# Find wheel
print('Finding wheels...')
wheels = glob.glob(osp.join(PACKAGE_ROOT, 'dist', '*.whl'))
output_dir = osp.join(PACKAGE_ROOT, 'dist', '.wheel-process')
image_binary = 'image.pyd'
video_binary = 'video_reader.pyd'
torchvision_binaries = [image_binary, video_binary]
for wheel in wheels:
if osp.exists(output_dir):
shutil.rmtree(output_dir)
os.makedirs(output_dir)
print('Unzipping wheel...')
wheel_file = osp.basename(wheel)
wheel_dir = osp.dirname(wheel)
print('{0}'.format(wheel_file))
wheel_name, _ = osp.splitext(wheel_file)
unzip_file(wheel, output_dir)
print('Finding DLL/PE dependencies...')
output_library = osp.join(output_dir, 'torchvision')
for binary in torchvision_binaries:
if osp.exists(osp.join(output_library, binary)):
relocate_dll_library(
dumpbin, output_dir, output_library, binary)
compress_wheel(output_dir, wheel, wheel_dir, wheel_name)
if __name__ == '__main__':
if sys.platform == 'linux':
patch_linux()
elif sys.platform == 'win32':
patch_win()
...@@ -49,8 +49,8 @@ size_t fillAudioTensor(DecoderOutputMessage& msgs, torch::Tensor& audioFrame) { ...@@ -49,8 +49,8 @@ size_t fillAudioTensor(DecoderOutputMessage& msgs, torch::Tensor& audioFrame) {
return fillTensorList<float>(msgs, audioFrame); return fillTensorList<float>(msgs, audioFrame);
} }
std::pair<std::string, ffmpeg::MediaType> const* _parse_type( std::array<std::pair<std::string, ffmpeg::MediaType>, 4>::const_iterator
const std::string& stream_string) { _parse_type(const std::string& stream_string) {
static const std::array<std::pair<std::string, MediaType>, 4> types = {{ static const std::array<std::pair<std::string, MediaType>, 4> types = {{
{"video", TYPE_VIDEO}, {"video", TYPE_VIDEO},
{"audio", TYPE_AUDIO}, {"audio", TYPE_AUDIO},
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment