Skip to content

Instantly share code, notes, and snippets.

@ales-erjavec
Last active June 15, 2016 15:52
Show Gist options
  • Save ales-erjavec/9d636f3172ab07261716618f0855dc55 to your computer and use it in GitHub Desktop.
Save ales-erjavec/9d636f3172ab07261716618f0855dc55 to your computer and use it in GitHub Desktop.
Convert a PyQt4/PyQt5 official windows installer into a pip installable wheel file
#! /bin/bash
set -e
function print_usage {
echo 'pyqt-convert.sh INSTALLER
Convert a PyQt4/PyQt5 windows installer into a wheel package.
note: 7z executable must be on PATH.
Options:
-d -dist-dir DIR Directory where the build .whl package is put (the
default is "dist")
-h --help Print this help and exit.
'
}
while [ "${1:0:1}" == "-" ]
do
case $1 in
-d|--dist-dir)
DISTDIR=${2:?"--dist-dir requires an argument"}
shift 2;;
--dist-dir=*)
DISTDIR=${1##*=}
shift 1;;
-h|-help)
print_usage
exit 0 ;;
-*)
echo "Unrecognized option $1"
print_usage
exit 1 ;;
esac
done
INSTALLER=${1:?"Missing a required positional argument"}
DISTDIR=${DISTDIR:-dist}
if [[ ! -f "$INSTALLER" ]]; then
echo "$INSTALLER does not exist"
print_usage
exit 1
fi
INSTBASE=$(basename "$INSTALLER")
# Get the PyQt4 version, python version, and platform tag from the installer
# filename.
# PyQt4-{VERSION}-gpl-Py{PYVER}-{QTVER}-(x32|x64).exe
NAME=${INSTBASE%%-*}
VERSION=$(echo "$INSTBASE" | cut -d "-" -f 2)
PYVERSION=$(echo "$INSTBASE" | cut -d "-" -f 4 | sed s/\\.//g | awk '{print tolower($0)}')
PYTAG=cp${PYVERSION##py}
PLATTAG=$(echo "$INSTBASE" | cut -d "-" -f 6 | cut -d "." -f 1)
echo "
NAME=${NAME}
VERSION=${VERSION}
PYVERSION=${PYVERSION}
PLATTAG=${PLATTAG}
"
case $PLATTAG in
x32)
PLATTAG=win32
;;
x64)
PLATTAG=win_amd64
;;
*)
echo "Unrecognized platform tag $PLATTAG"
exit 1;
esac
mkdir -p "$DISTDIR"
function extract-installer {
local installer=${1:?}
local wheelbase=${2:?}
local base=$(basename "${installer}")
local name=${base%%-*}
local tmpdir=$(mktemp -d -t "${base}")
7z -o"${tmpdir:?}" -y x "$installer"
cp -a -f "$tmpdir"/Lib/site-packages/* "$wheelbase"/
# Some (older?) installers had .pyd files packaged separately
if [[ -d "$tmpdir"/'$_OUTDIR' ]]; then
# * .pyd, doc/, examples/, mkspecs/, qsci/, include/, uic/
cp -a -f "$tmpdir"/'$_OUTDIR'/*.pyd "$wheelbase"/${name}/
cp -a -f "$tmpdir"/'$_OUTDIR'/uic "$wheelbase"/${name}/
# ignore the rest
fi
rm -r "$tmpdir"
rm "$wheelbase"/${name}/Uninstall.exe || true
rm -r "$wheelbase"/${name}/{doc,sip,mkspecs,examples}
}
function wheel-convert {
local installer=${1:?}
local distdir=${2:?}
local base=$(basename "${installer}")
local name=${base%%-*}
local version=$VERSION
local pytag=$PYTAG
local plattag=$PLATTAG
mkdir -p "$distdir"
local wheelbase=$(mktemp -d -t ${name}-wheel-convert)
mkdir -p "${wheelbase:?}"/${name}
mkdir -p "$wheelbase"/${name}-${version}.dist-info
mkdir -p "$wheelbase"/${name}-${version}.data/data
mkdir -p "$wheelbase"/${name}-${version}.data/data/Scripts
extract-installer "$installer" "$wheelbase"
echo "[PATHS]
Prefix = Lib/site-packages/${name}
" > "$wheelbase"/${name}-${version}.data/data/qt.conf
echo "[PATHS]
Prefix = ../Lib/site-packages/${name}
" > "$wheelbase"/${name}-${version}.data/data/Scripts/qt.conf
echo "[PATHS]
Prefix = .
" > "$wheelbase"/${name}/qt.conf
echo "Wheel-Version: 1.0
Generator: pyqt-convert.sh
Root-Is-Purelib: false
Tag: ${pytag}-none-${plattag}
Build: 1" > "${wheelbase}"/${name}-${version}.dist-info/WHEEL
echo "Metadata-Version: 1.1
Name: ${name}
Version: ${version}
Summary: xxx
Home-page: xxx
Author: xxx
Author-email: xxx
License: GPLv3
Download-URL: xxx
Description: xxx
" > "${wheelbase}"/${name}-${version}.dist-info/METADATA
local qtver=${name#Py}
cat <<EOF > "${wheelbase}"/${name}-${version}.dist-info/metadata.json
{
"generator": "pyqt-convert.sh (0.0.0)",
"summary": "Python bindings for ${qtver}.",
"classifiers": [
"Environment :: X11 Applications :: Qt"
],
"extensions": {
"python.details": {
"project_urls": {
"Home": "http://riverbankcomputing.com/"
},
"contacts": []
},
"python.commands": {
"wrap_console": [{"pyuic${version%%.*}": "${name}.uic.pyuic"}]
}
},
"keywords": [
"${qtver}", "GUI", "bindings"
],
"license": "GPLv3+",
"metadata_version": "2.0",
"name": "${name}",
"run_requires": [
{
"requires": []
}
],
"extras": [],
"version": "${version}"
}
EOF
generate_record "$wheelbase" > "$wheelbase"/${name}-${version}.dist-info/RECORD
echo "${name}-${version}.dist-info/RECORD,," >> "$wheelbase"/${name}-${version}.dist-info/RECORD
local wheelname=${name}-${version}-${pytag}-none-${plattag}.whl
wheel-zip "$wheelbase" "$wheelname"
mv "$wheelbase/$wheelname" "$distdir"
# cleanup tmp work dir
rm -r "$wheelbase"
}
function wheel-zip {
local wheelbase=${1:?}
local wheelname=${2:?}
wheelbase=$(cd "${wheelbase:?}"; pwd;)
(cd "$wheelbase"; find . -print | zip "$wheelname" -@)
}
function find_all {
(cd "${1:?}"; find . ;)
}
function urlsafe_b64encode_nopad {
python3 -c "import base64; print(base64.urlsafe_b64encode(bytes.fromhex('$1')).rstrip(b'=').decode())"
}
function generate_record {
local wheelbase=$1
find_all "$wheelbase" | while read line
do
line=${line##./} # strip the leading ./..
local filepath="$wheelbase"/"$line"
if [[ -f "$filepath" ]]
then
local sha=$(_sha256sum "$filepath")
local size=$(stat -f "%z" "$filepath")
echo "$line,sha256=$sha,$size"
fi
done
}
# _sha256sum PATH
# Return the sha256 checksum of PATH encoded with urlsafe_b64encode
# suitable for wheel RECORD file (see PEP 427 for details)
function _sha256sum {
python -c"
import sys, hashlib, base64
sha = hashlib.sha256()
for line in open(sys.argv[1], \"rb\"):
sha.update(line)
print(base64.urlsafe_b64encode(sha.digest()).rstrip(b'=').decode())
" "${1:?}"
}
wheel-convert "$INSTALLER" "$DISTDIR"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment