#!/usr/bin/env bash usage() { echo "usage:" echo " $0 [--no-di] META-NAME|YAMLFILE" echo "" echo "options:" echo " --no-di disable desktop integration" exit 1 } if [ $# -eq 0 ] || [ "x${!#}" = "x--no-di" ] ; then usage fi if [ $# -eq 2 ] && [ "x$1" != "x--no-di" ] ; then usage fi if [ "x$1" = "x--no-di" ] ; then ENABLE_DI="no" else ENABLE_DI="yes" fi # Halt on errors set -e set -x # Check dependencies which wget >/dev/null 2>&1 || ( echo wget missing && exit 1 ) which grep >/dev/null 2>&1 || ( echo grep missing && exit 1 ) which sed >/dev/null 2>&1 || ( echo sed missing && exit 1 ) which cut >/dev/null 2>&1 || ( echo cut missing && exit 1 ) # If the yaml file doesn't exist locally, get it from GitHub if [ ! -f "${!#}" ] ; then YAMLFILE=/tmp/_recipe.yml rm -f "$YAMLFILE" wget -q "https://github.com/probonopd/AppImages/raw/master/recipes/meta/${!#}.yml" -O "$YAMLFILE" else YAMLFILE=$(readlink -f "${!#}") fi # Lightweight bash-only dpkg-scanpackages replacement scanpackages() { for deb in *.deb ; do dpkg -I $deb | sed 's/^ *//g' | grep -i -E '(package|version|installed-size|architecture|depends|priority):' echo "Filename: $(readlink -f $deb)" echo "MD5sum: $(md5sum -b $deb | cut -d' ' -f1)" echo "SHA1: $(sha1sum -b $deb | cut -d' ' -f1)" echo "SHA256: $(sha256sum -b $deb | cut -d' ' -f1)" echo done } # Function to parse yaml # https://gist.github.com/epiloque/8cf512c6d64641bde388 # based on https://gist.github.com/pkuczynski/8665367 parse_yaml() { local prefix=$2 local s local w local fs s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs="$(echo @|tr @ '\034')" sed -ne "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \ -e "s|^\($s\)\($w\)$s[:-]$s\(.*\)$s\$|\1$fs\2$fs\3|p" "$1" | awk -F"$fs" '{ indent = length($1)/2; vname[indent] = $2; for (i in vname) {if (i > indent) {delete vname[i]}} if (length($3) > 0) { vn=""; for (i=0; i/dev/null || true generate_status # Some packages depend on packages which we do not want to bundle, # in addition to the global excludes defined in excludedeblist. # Use # ingredients: # exclude: # - packagename if [ ! -z "${_ingredients_exclude[0]}" ] ; then for PACKAGE in "${_ingredients_exclude[@]}" ; do printf "Package: $PACKAGE\nStatus: install ok installed\nArchitecture: all\nVersion: 9:999.999.999\n\n" >> status done fi # Some packages depend on an exact version of a dependency to be installed. # Use # ingredients: # pretend: # - packagename version_to_be_pretended if [ ! -z "${_ingredients_pretend[0]}" ] ; then for PRETEND in "${_ingredients_pretend[@]}" ; do P_PKG=$(echo "$PRETEND" | cut -d " " -f 1) P_VER=$(echo "$PRETEND" | cut -d " " -f 2) cat status | tr '\n' '@' | sed -e 's|@@|\n\n|g' | sed -e 's|Package: '"$P_PKG"'@Status: install ok installed@Architecture: all@Version: 9:999.999.999|Package: '"$P_PKG"'@Status: install ok installed@Architecture: all@Version: '"$P_VER"'|g' | sed -e 's|@|\n|g' > status.temp mv status.temp status done fi if [ -e sources.list ] ; then rm sources.list fi for SOURCE in "${_ingredients_sources[@]}" ; do echo "${SOURCE}" >> sources.list done for PPA in "${_ingredients_ppas[@]}" ; do echo "deb http://ppa.launchpad.net/${PPA}/ubuntu ${_ingredients_dist} main" >> sources.list done # Use libcurl-slim to reduce AppImage size, thanks darealshinji # Not really compiled on xenial but CentOS 6, https://github.com/AppImage/AppImages/issues/187 echo "deb http://ppa.launchpad.net/djcj/libcurl-slim/ubuntu xenial main" >> sources.list fi if [ ! -z "${_ingredients_script[0]}" ] ; then # Execute extra steps defined in recipe shell_execute $YAMLFILE _ingredients_script fi if [ ! -z "${_ingredients_dist}" ] ; then # Some projects provide raw .deb files without a repository # hence we create our own local repository as part of # the AppImage creation process in order to "install" # the package using apt-get as normal if [ ! -z "${_ingredients_debs[0]}" ] ; then for DEB in "${_ingredients_debs[@]}" ; do if [ ! -f $(basename "$DEB") ] ; then wget -c $DEB fi done fi scanpackages | gzip -9c > Packages.gz echo "deb file:$(readlink -e $PWD) ./" >> sources.list INSTALL=$LOWERAPP if [ ! -z "${_ingredients_package}" ] ; then INSTALL="${_ingredients_package}" fi if [ ! -z "${_ingredients_packages}" ] ; then INSTALL="" fi # If packages are specifically listed, only install these, not a package with the name of the app if [ ! -z "${_ingredients_packages[0]}" ] ; then INSTALL=${_ingredients_packages[@]} fi apt-get $OPTIONS update || true URLS=$(apt-get $OPTIONS -y install --print-uris $INSTALL | cut -d "'" -f 2 | grep -e "^http") if which aria2c &>/dev/null; then dltool=aria2c else dltool=wget fi $dltool -c -i- <<<"$URLS" fi mkdir -p ./$APP.AppDir/ cd ./$APP.AppDir/ mkdir -p usr/bin usr/lib find ../*.deb -exec dpkg -x {} . \; || true # Try to copy icons to standard locations where appimaged can pick them up mkdir -p usr/share/icons/hicolor/{22x22,24x24,32x32,48x48,64x64,128x128,256x256,512x512}/apps/ find . -path *icons* -path *22* -name "*$LOWERAPP*" -exec cp {} usr/share/icons/hicolor/22x22/apps/ \; || true find . -path *icons* -path *24* -name "*$LOWERAPP*" -exec cp {} usr/share/icons/hicolor/24x24/apps/ \; || true find . -path *icons* -path *32* -name "*$LOWERAPP*" -exec cp {} usr/share/icons/hicolor/32x32/apps/ \; || true find . -path *icons* -path *48* -name "*$LOWERAPP*" -exec cp {} usr/share/icons/hicolor/48x48/apps/ \; || true find . -path *icons* -path *64* -name "*$LOWERAPP*" -exec cp {} usr/share/icons/hicolor/64x64/apps/ \; || true find . -path *icons* -path *128* -name "*$LOWERAPP*" -exec cp {} usr/share/icons/hicolor/128x128/apps/ \; || true find . -path *icons* -path *256* -name "*$LOWERAPP*" -exec cp {} usr/share/icons/hicolor/256x256/apps/ \; || true find . -path *icons* -path *512* -name "*$LOWERAPP*" -exec cp {} usr/share/icons/hicolor/512x512/apps/ \; || true get_icon if [ -z "${_union}" ] ; then get_apprun else cat > AppRun <<\EOF #!/bin/sh HERE="$(dirname "$(readlink -f "${0}")")" export UNION_PRELOAD="${HERE}" export LD_PRELOAD="${HERE}/libunionpreload.so" export PATH="${HERE}"/usr/bin/:"${HERE}"/usr/sbin/:"${HERE}"/usr/games/:"${HERE}"/bin/:"${HERE}"/sbin/:"${PATH}" export LD_LIBRARY_PATH="${HERE}"/usr/lib/:"${HERE}"/usr/lib/i386-linux-gnu/:"${HERE}"/usr/lib/x86_64-linux-gnu/:"${HERE}"/usr/lib32/:"${HERE}"/usr/lib64/:"${HERE}"/lib/:"${HERE}"/lib/i386-linux-gnu/:"${HERE}"/lib/x86_64-linux-gnu/:"${HERE}"/lib32/:"${HERE}"/lib64/:"${LD_LIBRARY_PATH}" export PYTHONPATH="${HERE}"/usr/share/pyshared/:"${PYTHONPATH}" export PYTHONHOME="${HERE}"/usr/ export XDG_DATA_DIRS="${HERE}"/usr/share/:"${XDG_DATA_DIRS}" export PERLLIB="${HERE}"/usr/share/perl5/:"${HERE}"/usr/lib/perl5/:"${PERLLIB}" export GSETTINGS_SCHEMA_DIR="${HERE}"/usr/share/glib-2.0/schemas/:"${GSETTINGS_SCHEMA_DIR}" export QT_PLUGIN_PATH="${HERE}"/usr/lib/qt4/plugins/:"${HERE}"/usr/lib/i386-linux-gnu/qt4/plugins/:"${HERE}"/usr/lib/x86_64-linux-gnu/qt4/plugins/:"${HERE}"/usr/lib32/qt4/plugins/:"${HERE}"/usr/lib64/qt4/plugins/:"${HERE}"/usr/lib/qt5/plugins/:"${HERE}"/usr/lib/i386-linux-gnu/qt5/plugins/:"${HERE}"/usr/lib/x86_64-linux-gnu/qt5/plugins/:"${HERE}"/usr/lib32/qt5/plugins/:"${HERE}"/usr/lib64/qt5/plugins/:"${QT_PLUGIN_PATH}" EXEC=$(grep -e '^Exec=.*' "${HERE}"/*.desktop | head -n 1 | cut -d "=" -f 2- | sed -e 's|%.||g') exec ${EXEC} $@ EOF chmod a+x AppRun fi get_desktop # Prevent Qt from loading plugins from the system unset QTPATH QTPATH=$(find usr/lib -type d -name qt4 -or -name qt5 | sed -e 's|usr/|../|g') if [ ! -z $QTPATH ] ; then cat > usr/bin/qt.conf < temp \; # Remove all absolute paths sed -i -E 's|target=\"\/(.*\/)([a-z0-9].*?)>|target=\"\2>|g' temp SONAMES=$(cat temp | cut -d '"' -f 4 | grep ".so") for SONAME in $SONAMES; do find . -name "$SONAME" -exec mv {} usr/lib \; done rm temp PATH_OF_THE_EXE="usr/lib/mono/exe" mkdir -p "PATH_OF_THE_EXE" # Force all dll files into PATH_OF_THE_EXE (or MONO_PATH which we would have to set) find . -name "*.dll" -and -not -name "mscorlib.dll" -exec mv {} "$PATH_OF_THE_EXE" \; # Edit all config files in place to remove absolute paths find . -name "*.dll.config" -exec sed -i -E 's|target=\"\/(.*\/)([a-z0-9].*?)>|target=\"\2>|g' {} \; # Force all config files into the PATH_OF_THE_EXE (or MONO_PATH which we would have to set) find . -name "*.dll.config" -exec mv {} "$PATH_OF_THE_EXE" \; # Remove gac, we are not using it since it is convoluted rm -rf usr/lib/mono/gac/ fi if [ -d "./usr/lib/x86_64-linux-gnu/gstreamer-1.0/" ] ; then mv ./usr/lib/x86_64-linux-gnu/gstreamer-1.0/* ./usr/lib/x86_64-linux-gnu/ rm -r ./usr/lib/x86_64-linux-gnu/gstreamer-1.0 fi if [ -d "./usr/lib/x86_64-linux-gnu/pulseaudio/" ] ; then mv ./usr/lib/x86_64-linux-gnu/pulseaudio/* ./usr/lib/x86_64-linux-gnu/ rm -r ./usr/lib/x86_64-linux-gnu/pulseaudio fi # Execute extra steps defined in recipe if [ ! -z "${_script}" ] ; then shell_execute $YAMLFILE _script fi DESKTOP=$(find . -name '*.desktop' | sort | head -n 1) # desktop-file-validate complains about missing trailing semicolons for some # keys although the format definition says that they are optional fix_desktop "$DESKTOP" # Some non-distribution provided applications have an absolute # path in the Exec= line which we remove for relocateability if [ -z "$DESKTOP" ] ; then echo "desktop file not found, aborting" exit 1 else desktop-file-validate "$DESKTOP" || exit 1 ORIG=$(grep -o "^Exec=.*$" "${DESKTOP}" | head -n 1| cut -d " " -f 1) REPL=$(basename $(grep -o "^Exec=.*$" "${DESKTOP}" | head -n 1 | cut -d " " -f 1 | sed -e 's|Exec=||g')) sed -i -e 's|'"${ORIG}"'|Exec='"${REPL}"'|g' "${DESKTOP}" fi # Compile GLib schemas if the subdirectory is present in the AppImage # AppRun has to export GSETTINGS_SCHEMA_DIR for this to work if [ -d usr/share/glib-2.0/schemas/ ] ; then ( cd usr/share/glib-2.0/schemas/ ; glib-compile-schemas . ) fi if [ -f ../VERSION ] ; then VERSION=$(cat ../VERSION) else get_version || true fi # patch_usr # Patching only the executable files seems not to be enough for some apps if [ ! -z "${_binpatch}" ] ; then find usr/ -type f -exec sed -i -e 's|/usr|././|g' {} \; find usr/ -type f -exec sed -i -e 's@././/bin/env@/usr/bin/env@g' {} \; fi # Don't suffer from NIH; use LD_PRELOAD to override calls to /usr paths if [ ! -z "${_union}" ] ; then mkdir -p usr/src/ wget -q "https://raw.githubusercontent.com/mikix/deb2snap/master/src/preload.c" -O - | \ sed -e 's|SNAPPY|UNION|g' | sed -e 's|SNAPP|UNION|g' | sed -e 's|SNAP|UNION|g' | \ sed -e 's|snappy|union|g' > usr/src/libunionpreload.c gcc -shared -fPIC usr/src/libunionpreload.c -o libunionpreload.so -ldl -DUNION_LIBNAME=\"libunionpreload.so\" strip libunionpreload.so fi delete_blacklisted if [ "$ENABLE_DI" = "yes" ] ; then get_desktopintegration $LOWERAPP fi # Fix desktop files that have file endings for icons sed -i -e 's|\.png||g' *.desktop || true sed -i -e 's|\.svg||g' *.desktop || true sed -i -e 's|\.svgz||g' *.desktop || true sed -i -e 's|\.xpm||g' *.desktop || true # Setting PYTHONHOME instead # Fix Python imports, # https://github.com/probonopd/AppImages/issues/172 # SITECUSTOMIZEFILES=$(find . -name "sitecustomize.py") # for SITECUSTOMIZEFILE in $SITECUSTOMIZEFILES ; do # rm $SITECUSTOMIZEFILE # Remove symlinks, replace by files # cat > $SITECUSTOMIZEFILE <<\EOF # import sys,os # if sys.version_info[0] < 3: # prefix = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(sys.path[0])))) # sys.path = [ prefix+s for s in sys.path if not s.startswith(prefix) ] # EOF # done # Execute extra steps defined in recipe if [ ! -z "${_post}" ] ; then shell_execute $YAMLFILE _post fi # Go out of AppImage cd .. generate_type2_appimage ls -lh ../out/*.AppImage