Skip to content

Commit 6ec4156

Browse files
committed
Rebase the Python install and patch binaries
1 parent a248746 commit 6ec4156

8 files changed

Lines changed: 122 additions & 36 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ __pycache__
22
*.pyc
33
*.pyo
44
appimage/*.AppImage
5+
share/excludelist
56
wiki

.travis/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ RUN apt update -y -qq && \
88
apt install --no-install-recommends -y -qq \
99
build-essential libssl-dev zlib1g-dev libncurses5-dev libncursesw5-dev \
1010
libreadline-dev libsqlite3-dev libgdbm-dev libdb5.3-dev libbz2-dev \
11-
libexpat1-dev liblzma-dev libffi-dev tk-dev libfuse2 wget && \
11+
libexpat1-dev liblzma-dev libffi-dev tk-dev automake libfuse2 wget && \
1212
rm -rf /var/lib/apt/lists/*
1313

1414
COPY . /work/.travis

.travis/script.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ if [ -n "${OPENSSL}" ]; then
1212
fi
1313

1414

15-
DEBUG=true ./appimage/build-plugin.sh
16-
DEBUG=true ./appimage/build-python.sh python2
17-
DEBUG=true ./appimage/build-python.sh python3
18-
DEBUG=true ./appimage/build-python.sh scipy
15+
./appimage/build-plugin.sh
16+
./appimage/build-python.sh python2
17+
./appimage/build-python.sh python3
18+
./appimage/build-python.sh scipy
1919

2020
python3 -m tests

appimage/build-plugin.sh

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ fi
99

1010
EXEC_NAME="${EXEC_NAME:-linuxdeploy-plugin-python}"
1111
ARCH="${ARCH:-$(arch)}"
12+
PATCHELF_VERSION="0.10"
1213

1314
REPO_ROOT=$(readlink -f $(dirname "$0")"/..")
1415

@@ -30,6 +31,32 @@ fi
3031
pushd $BUILD_DIR
3132

3233

34+
# Install the ELF patcher
35+
prefix="${PWD}/AppDir/usr"
36+
mkdir -p "${prefix}"
37+
wget -cq --no-check-certificate "https://github.com/NixOS/patchelf/archive/${PATCHELF_VERSION}.tar.gz"
38+
tar -xzf "${PATCHELF_VERSION}.tar.gz"
39+
pushd "patchelf-${PATCHELF_VERSION}"
40+
./bootstrap.sh
41+
if [ "$ARCH" == "i386" ]; then
42+
./configure --prefix="${prefix}" --build=i686-pc-linux-gnu CFLAGS=-m32 CXXFLAGS=-m32 LDFLAGS=-m32
43+
elif [ "$ARCH" == "x86_64" ]; then
44+
./configure --prefix="${prefix}"
45+
fi
46+
make -j$(nproc)
47+
make install
48+
popd
49+
rm -rf "AppDir/usr/share"
50+
strip "AppDir/usr/bin/patchelf"
51+
52+
53+
# Package the exclusion list
54+
mkdir -p "AppDir/share"
55+
pushd "AppDir/share"
56+
wget -cq --no-check-certificate "https://raw.githubusercontent.com/probonopd/AppImages/master/excludelist"
57+
popd
58+
59+
3360
# Build the AppImage
3461
appimagetool="appimagetool-${ARCH}.AppImage"
3562

@@ -39,7 +66,6 @@ if [ ! -f "${appimagetool}" ]; then
3966
chmod u+x "${appimagetool}"
4067
fi
4168

42-
mkdir -p "AppDir/share"
4369
cp "${REPO_ROOT}/${EXEC_NAME}.sh" "AppDir/AppRun"
4470
chmod +x "AppDir/AppRun"
4571
cp -r "${REPO_ROOT}/share" "AppDir"

appimage/build-python.sh

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,9 @@ sed -i "s|[{][{]exe[}][}]|${exe}|g" "${name}.desktop"
7070
sed -i "s|[{][{]name[}][}]|${name}|g" "${name}.desktop"
7171

7272
./"${linuxdeploy}" --appdir AppDir \
73-
--plugin python
74-
./"${linuxdeploy}" --appdir AppDir \
73+
--plugin python \
7574
-i "${REPO_ROOT}/appimage/resources/python.png" \
7675
-d "${name}.desktop" \
77-
-e "AppDir/usr/bin/.${exe}" \
7876
--custom-apprun "AppDir/usr/bin/${exe}" \
7977
--output "appimage"
8078

linuxdeploy-plugin-python.sh

Lines changed: 79 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -119,61 +119,120 @@ if [[ "${source_dir}" == *.tgz ]] || [[ "${source_dir}" == *.tar.gz ]]; then
119119
source_dir="$filename"
120120
fi
121121

122+
prefix="usr/python"
122123
cd "${source_dir}"
123-
./configure ${PYTHON_CONFIG} "--with-ensurepip=install" "--prefix=/usr"
124+
./configure ${PYTHON_CONFIG} "--with-ensurepip=install" "--prefix=/${prefix}"
124125
HOME="${PYTHON_BUILD_DIR}" make -j"$NPROC" DESTDIR="$APPDIR" install
125126

126127

127-
# Copy any TCl/Tk shared data
128-
if [ -d "/usr/share/tcltk" ]; then
129-
cp -r "/usr/share/tcltk" "${APPDIR}/usr/share/tcltk"
130-
fi
131-
132-
133128
# Install any extra requirements with pip
134129
if [ ! -z "${PIP_REQUIREMENTS}" ]; then
135-
cd "${APPDIR}/usr"
130+
cd "${APPDIR}/${prefix}/bin"
136131
pythons=( "python"?"."? )
137-
HOME="${PYTHON_BUILD_DIR}" PYTHONHOME=$PWD ./bin/${pythons[0]} -m pip install ${PIP_OPTIONS} ${PIP_REQUIREMENTS}
132+
HOME="${PYTHON_BUILD_DIR}" PYTHONHOME=$(readlink -f ${PWD}/..) ./${pythons[0]} -m pip install ${PIP_OPTIONS} ${PIP_REQUIREMENTS}
138133
fi
139134

140135

141136
# Prune the install
142-
cd "$APPDIR/usr"
137+
cd "$APPDIR/${prefix}"
143138
rm -rf "bin/python"*"-config" "bin/idle"* "include" "lib/pkgconfig" \
144139
"share/doc" "share/man" "lib/libpython"*".a" "lib/python"*"/test" \
145140
"lib/python"*"/config-"*"-x86_64-linux-gnu"
146141

147142

148143
# Wrap the Python executables
149-
cd "$APPDIR/usr/bin"
144+
cd "$APPDIR/${prefix}/bin"
150145
set +e
151146
pythons=$(ls "python" "python"? "python"?"."? "python"?"."?"m" 2>/dev/null)
152147
set -e
148+
cd "$APPDIR/usr/bin"
153149
for python in $pythons
154150
do
155151
if [ ! -L "$python" ]; then
156-
mv "$python" ".$python"
152+
strip "$APPDIR/${prefix}/bin/${python}"
157153
cp "${BASEDIR}/share/python-wrapper.sh" "$python"
158154
sed -i "s|[{][{]PYTHON[}][}]|$python|g" "$python"
155+
sed -i "s|[{][{]PREFIX[}][}]|$prefix|g" "$python"
159156
fi
160157
done
161158

162159

163160
# Sanitize the shebangs of local Python scripts
164-
for exe in $(ls "${APPDIR}/usr/bin"*)
161+
cd "$APPDIR/${prefix}/bin"
162+
for exe in $(ls "${APPDIR}/${prefix}/bin"*)
165163
do
166-
sed -i '1s|^#!.*\(python[0-9.]*\)|#!/bin/sh\n"exec" "$(dirname $(readlink -f $\{0\}))/\1" "$0" "$@"|' "$exe"
164+
sed -i '1s|^#!.*\(python[0-9.]*\)|#!/bin/sh\n"exec" "$(dirname $(readlink -f $\{0\}))/../../bin/\1" "$0" "$@"|' "$exe"
167165
done
168166

169167

170168
# Set a hook in Python for cleaning the path detection
171-
cp "$BASEDIR/share/sitecustomize.py" "$APPDIR"/usr/lib/python*/site-packages
169+
cp "$BASEDIR/share/sitecustomize.py" "$APPDIR"/${prefix}/lib/python*/site-packages
170+
172171

172+
# Patch binaries and install dependencies
173+
excludelist=$(cat "${BASEDIR}/share/excludelist" | sed 's|#.*||g' | sed -r '/^\s*$/d')
173174

174-
# Relocate the Python extension modules for the dynamic linker
175+
is_excluded () {
176+
local e
177+
for e in ${excludelist}; do
178+
[[ "$e" == "$1" ]] && echo "true" && return 0
179+
done
180+
return 0
181+
}
182+
183+
set +e
184+
patchelf=$(command -v patchelf)
185+
set -e
186+
patchelf="${patchelf:-${BASEDIR}/usr/bin/patchelf}"
187+
188+
patch_binary() {
189+
local name="$(basename $1)"
190+
191+
if [ "${name::3}" == "lib" ]; then
192+
if [ ! -f "${APPDIR}/usr/lib/${name}" ]; then
193+
echo "Patching dependency ${name}"
194+
strip "$1"
195+
"${patchelf}" --set-rpath '$ORIGIN' "$1"
196+
ln -rs "$1" "${APPDIR}/usr/lib"
197+
fi
198+
else
199+
echo "Patching C-extension module ${name}"
200+
strip "$1"
201+
202+
local rel=$(dirname $(readlink -f $1))
203+
rel=${rel#${APPDIR}/usr}
204+
rel=$(echo $rel | sed 's|/[_a-zA-Z0-9.-]*|/..|g')
205+
"${patchelf}" --set-rpath '$ORIGIN:$ORIGIN'"${rel}/lib" "$1"
206+
fi
207+
208+
local deps
209+
for deps in $(ldd $1); do
210+
if [[ "${deps::1}" == "/" ]] && [[ "${deps}" != "${APPDIR}"* ]]; then
211+
local lib="$(basename ${deps})"
212+
if [ ! -f "${APPDIR}/usr/lib/${lib}" ]; then
213+
if [ ! "$(is_excluded ${lib})" ]; then
214+
echo "Installing dependency ${lib}"
215+
cp "${deps}" "${APPDIR}/usr/lib"
216+
strip "${APPDIR}/usr/lib/${lib}"
217+
"${patchelf}" --set-rpath '$ORIGIN' "${APPDIR}/usr/lib/${lib}"
218+
fi
219+
fi
220+
fi
221+
done
222+
return 0
223+
}
224+
225+
cd "$APPDIR/${prefix}/bin"
175226
python=$(ls "python"?"."?)
176-
cd "$APPDIR/usr/lib"
177-
mv "${python}/lib-dynload/"* "."
178-
rm -rf "${python}/lib-dynload"
179-
ln -fs "../" "${python}/lib-dynload"
227+
mkdir -p "${APPDIR}/usr/lib"
228+
cd "${APPDIR}/${prefix}/lib/${python}"
229+
find "lib-dynload" -name '*.so' -type f | while read file; do patch_binary "${file}"; done
230+
find "site-packages" -name '*.so' -type f | while read file; do patch_binary "${file}"; done
231+
find "site-packages" -name 'lib*.so*' -type f | while read file; do patch_binary "${file}"; done
232+
233+
234+
# Copy any TCl/Tk shared data
235+
if [[ -d "/usr/share/tcltk" ]] && [[ ! -d "${APPDIR}/${prefix}/share/tcltk" ]]; then
236+
mkdir -p "${APPDIR}/${prefix}/share"
237+
cp -r "/usr/share/tcltk" "${APPDIR}/${prefix}/share"
238+
fi

share/python-wrapper.sh

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
#!/bin/bash
22

33
# Configure the environment
4-
export TCL_LIBRARY="${APPDIR}/usr/share/tcltk/tcl8.6"
5-
export TK_LIBRARY="${APPDIR}/usr/share/tcltk/tk8.6"
4+
prefix="{{PREFIX}}"
5+
export TCL_LIBRARY="${APPDIR}/${prefix}/share/tcltk/tcl"*
6+
export TK_LIBRARY="${APPDIR}/${prefix}/share/tcltk/tk"*
67
export TKPATH="${TK_LIBRARY}"
78

89
# Resolve symlinks within the image
910
nickname="{{PYTHON}}"
10-
executable="${APPDIR}/usr/bin/${nickname}"
11+
executable="${APPDIR}/${prefix}/bin/${nickname}"
1112
if [ -L "${executable}" ]; then
1213
nickname="$(basename $(readlink -f ${executable}))"
1314
fi
@@ -18,7 +19,7 @@ do
1819
if [[ "${opt}" =~ "I" ]] || [[ "${opt}" =~ "E" ]]; then
1920
# Environment variables are disabled ($PYTHONHOME). Let's run in a safe
2021
# mode from the raw Python binary inside the AppImage
21-
"$APPDIR/usr/bin/.${nickname}" "$@"
22+
"$APPDIR/${prefix}/bin/${nickname}" "$@"
2223
exit "$?"
2324
fi
2425
done
@@ -33,5 +34,5 @@ fi
3334
# Wrap the call to Python in order to mimic a call from the source
3435
# executable ($ARGV0), but potentially located outside of the Python
3536
# install ($PYTHONHOME)
36-
(PYTHONHOME="${APPDIR}/usr" exec -a "${executable}" "$APPDIR/usr/bin/.${nickname}" "$@")
37+
(PYTHONHOME="${APPDIR}/${prefix}" exec -a "${executable}" "$APPDIR/${prefix}/bin/${nickname}" "$@")
3738
exit "$?"

tests/test_plugin.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,12 @@ def check_base(self, tag):
115115
v = [int(vi) for vi in version.split(".")]
116116
self.assertEqual(cfg["version"][:3], v)
117117
self.assertEqual(cfg["executable"], python)
118-
self.assertEqual(cfg["prefix"], os.path.join(cfg["appdir"], "usr"))
118+
self.assertEqual(cfg["prefix"], os.path.join(cfg["appdir"], "usr",
119+
"python"))
119120
site_packages = os.path.join("lib",
120121
"python{:}.{:}".format(*cfg["version"][:2]), "site-packages")
121122
self.assertEqual(cfg["path"][-1], os.path.join(cfg["appdir"],
122-
"usr", site_packages))
123+
"usr", "python", site_packages))
123124
user_packages = os.path.join(cfg["home"], ".local", site_packages)
124125
self.assertTrue(user_packages in cfg["path"])
125126

0 commit comments

Comments
 (0)