Tentative fix to include package dependencies.

This commit is contained in:
emiliano.vavassori 2024-08-23 11:38:58 +02:00
parent 3428c9fb53
commit 50e019262a
1 changed files with 133 additions and 74 deletions

View File

@ -91,11 +91,22 @@ class Build():
"""Calculate exclusions and other variables.""" """Calculate exclusions and other variables."""
if self.verbose: if self.verbose:
print("--- Preliminary system checks ---") print("--- Preliminary Phase ---")
if isinstance(shutil.which('apt'), str): if isinstance(shutil.which('apt'), str):
# APT is found in path. We assume we can find dependencies. # APT is found in path. We assume we can find dependencies.
self.check_dependencies = True self.check_dependencies = True
if self.verbose:
print("Updating system packages cache.")
# Updating package cache
subprocess.run(['sudo', 'apt', 'update'], check=True, stdout=subprocess.DEVNULL)
if self.verbose:
print("Ensuring apt-file is installed and updated.")
# Updating apt-file cache
subprocess.run(['sudo', 'apt', 'install', 'apt-file', '-y'], check=True, stdout=subprocess.DEVNULL)
subprocess.run(['sudo', 'apt-file', 'update'], check=True, stdout=subprocess.DEVNULL)
else: else:
print("CAUTION: your system seems not to include a working version of apt.\nThis will cause the AppImage to leverage system libraries when run.") print("CAUTION: your system seems not to include a working version of apt.\nThis will cause the AppImage to leverage system libraries when run.")
self.check_dependencies = False self.check_dependencies = False
@ -237,12 +248,12 @@ class Build():
def build(self): def build(self):
"""Building all the versions.""" """Building all the versions."""
if self.verbose:
print("--- Building Phase ---")
if self.found: if self.found:
return return
if self.verbose:
print("--- Building Phase ---")
# Preparation tasks # Preparation tasks
self.appnamedir = os.path.join(self.builddir, self.appname) self.appnamedir = os.path.join(self.builddir, self.appname)
os.makedirs(self.appnamedir, exist_ok=True) os.makedirs(self.appnamedir, exist_ok=True)
@ -256,6 +267,13 @@ class Build():
# Build the requested version. # Build the requested version.
self.__unpackbuild__() self.__unpackbuild__()
self.__prepare_contents__()
if self.check_dependencies:
if self.verbose:
print("Searching for dependent libraries, it might take a while.")
self.__missing_dependencies__()
self.__finalize_build__()
def checksums(self): def checksums(self):
@ -505,6 +523,7 @@ class Build():
subprocess.run(shlex.split( subprocess.run(shlex.split(
f"tar xzf {self.download_path}/{archive}"), check=True) f"tar xzf {self.download_path}/{archive}"), check=True)
def __prepare_contents__(self):
# create appimagedir # create appimagedir
if self.verbose: if self.verbose:
print("---- Preparing the build ----") print("---- Preparing the build ----")
@ -512,6 +531,9 @@ class Build():
os.makedirs(self.appimagedir, exist_ok = True) os.makedirs(self.appimagedir, exist_ok = True)
# At this point, let's decompress the deb packages # At this point, let's decompress the deb packages
if self.verbose:
print("Unpacking main archives")
subprocess.run(shlex.split( subprocess.run(shlex.split(
r"find .. -iname '*.deb' -exec dpkg -x {} . \;" r"find .. -iname '*.deb' -exec dpkg -x {} . \;"
), cwd=self.appimagedir, check=True) ), cwd=self.appimagedir, check=True)
@ -524,6 +546,9 @@ class Build():
), cwd=self.appimagedir, check=True) ), cwd=self.appimagedir, check=True)
# Changing desktop file # Changing desktop file
if self.verbose:
print("Preparing .desktop file.")
subprocess.run(shlex.split( subprocess.run(shlex.split(
r"find . -iname startcenter.desktop -exec cp {} . \;" r"find . -iname startcenter.desktop -exec cp {} . \;"
), cwd=self.appimagedir, check=True) ), cwd=self.appimagedir, check=True)
@ -533,6 +558,8 @@ class Build():
r"startcenter.desktop" r"startcenter.desktop"
), cwd=self.appimagedir, check=False) ), cwd=self.appimagedir, check=False)
if self.verbose:
print("Preparing icon file.")
subprocess.run(shlex.split( subprocess.run(shlex.split(
r"find . -name '*startcenter.png' -path '*hicolor*48x48*' " + r"find . -name '*startcenter.png' -path '*hicolor*48x48*' " +
r"-exec cp {} . \;" r"-exec cp {} . \;"
@ -542,62 +569,10 @@ class Build():
cmd = subprocess.run(shlex.split( cmd = subprocess.run(shlex.split(
r"find -iname soffice.bin -print" r"find -iname soffice.bin -print"
), cwd=self.appimagedir, check = True, capture_output=True) ), cwd=self.appimagedir, check = True, capture_output=True)
main_executable = os.path.abspath(os.path.join( self.main_executable = os.path.abspath(os.path.join(
self.appimagedir, self.appimagedir,
cmd.stdout.strip().decode('utf-8'))) cmd.stdout.strip().decode('utf-8')))
# If the system permits it, we leverage lddcollect
# to find the packages that contain .so dependencies in the main build.
if self.check_dependencies:
if self.verbose:
print("Checking for dependent libraries")
import lddcollect
# We first process the ELF
raw = lddcollect.process_elf(main_executable, verbose = False, dpkg = True)
# If all works as expected, we obtain a tuple of:
# (debian_packages, all_libraries, files_not_found)
(debian_packages, all_libraries, not_found) = raw
if len(debian_packages) != 0:
# We need, first, to download those packages.
debs = [ x.split(':')[0] for x in debian_packages ]
downloadpath = os.path.abspath(os.path.join(self.builddir, 'dependencies'))
os.makedirs(downloadpath)
if self.verbose:
print("Downloading missing dependencies, please wait.")
# Updating package cache
subprocess.run(['sudo', 'apt', 'update'], check=True)
# Updating apt-file cache
subprocess.run(['sudo', 'apt-file', 'update'], check=True)
# Let's try to find and install also other libraries
additional = list(dict.fromkeys([ libfinderhelper(x) for x in not_found ]))
debs.extend(additional)
# We download the missing dependencies leveraging apt
subprocess.run(shlex.split(
r"apt download " + " ".join(debs)
), cwd=downloadpath, check=True)
# then we install them inside a temporary path
temporary = os.path.abspath(os.path.join(downloadpath, 'temp'))
os.makedirs(temporary)
subprocess.run(shlex.split(
r"find " + downloadpath + r" -iname \*.deb -exec dpkg -x {} " + temporary + r" \;"
), cwd=self.builddir, check=True)
# We are finally copying the .so files in the same path as main_executable
libdirs = [ 'lib/x86_64-linux-gnu', 'usr/lib/x86_64-linux-gnu' ]
for libdir in libdirs:
fulllibdir = os.path.abspath(os.path.join(temporary, libdir))
subprocess.run(shlex.split(
f"cp -Ra {fulllibdir}/. {os.path.dirname(main_executable)}/"
), cwd=temporary, check=True)
# Find the name of the binary called in the desktop file. # Find the name of the binary called in the desktop file.
binaryname = '' binaryname = ''
with open( with open(
@ -621,6 +596,70 @@ class Build():
r"-exec ln -sf {} ./%s \;" % binaryname r"-exec ln -sf {} ./%s \;" % binaryname
), cwd=bindir, check=True) ), cwd=bindir, check=True)
def __missing_dependencies__(self):
"""Finds and copy in the appimagedir any missing libraries."""
# If the system permits it, we leverage lddcollect
# to find the packages that contain .so dependencies in the main build.
import lddcollect
# We first process the ELF
raw = lddcollect.process_elf(self.main_executable, verbose = False, dpkg = True)
# If all works as expected, we obtain a tuple of:
# (debian_packages, all_libraries, files_not_found)
(debian_packages, all_libraries, not_found) = raw
if len(debian_packages) != 0:
# Creating temporary folders
debs = [ x.split(':')[0] for x in debian_packages ]
downloadpath = os.path.abspath(os.path.join(self.builddir, 'dependencies'))
os.makedirs(downloadpath)
if self.verbose:
print("Downloading missing dependencies, please wait.")
# Let's try to find and install also other libraries
additional = list(dict.fromkeys([ Helpers.lib_to_deb(x) for x in not_found ]))
debs.extend(additional)
# It seems the download command does not download dependencies of
# the packages.
if self.verbose:
print("Constructing the dependency tree.")
for deb in debian_packages:
debs.extend(Helpers.deb_dependencies(deb))
# Re-cleaning up the dependency tree
debs = list(dict.fromkeys(debs))
# We download the missing dependencies leveraging apt
subprocess.run(shlex.split(
r"apt download " + " ".join(debs)
), cwd=downloadpath, check=True)
# then we install them inside a temporary path
temporary = os.path.abspath(os.path.join(downloadpath, 'temp'))
os.makedirs(temporary)
subprocess.run(shlex.split(
r"find " + downloadpath + r" -iname \*.deb -exec dpkg -x {} " + temporary + r" \;"
), cwd=self.builddir, check=True)
# We are finally copying the .so files in the same path as main_executable
libdirs = [ 'lib/x86_64-linux-gnu', 'usr/lib/x86_64-linux-gnu' ]
for libdir in libdirs:
fulllibdir = os.path.abspath(os.path.join(temporary, libdir))
subprocess.run(shlex.split(
f"cp -Ra {fulllibdir}/. {os.path.dirname(self.main_executable)}/"
), cwd=temporary, check=True)
if self.debug:
with open(os.path.abspath(os.builddir, 'dependencies.lst'), 'w', encoding="utf-8") as deplist:
deplist.write("\n".join(debs))
def __finalize_build__(self):
if self.verbose:
print("Finalizing build...")
# Cleaning up AppDir # Cleaning up AppDir
cleanup_dirs = [ 'etc', 'lib', 'lib64', 'usr/lib', 'usr/local' ] cleanup_dirs = [ 'etc', 'lib', 'lib64', 'usr/lib', 'usr/local' ]
for local in cleanup_dirs: for local in cleanup_dirs:
@ -678,11 +717,31 @@ class Build():
shutil.rmtree(self.builddir) shutil.rmtree(self.builddir)
def libfinderhelper(libraryname): class Helpers:
"""Uses system tools to identify the missing package."""
libsearch = subprocess.run(shlex.split( @staticmethod
f"sudo apt-file find -lx {libraryname}$" def deb_dependencies(package_name):
), check=True, capture_output=True) """Returns the array of the dependencies of that package."""
candidate = [ x for x in libsearch.stdout.decode('utf-8').split('\n') if 'lib' in x ][0]
return candidate # First pass: find dependency of that package in raw output
pass1 = subprocess.Popen(shlex.split(
f"apt-cache depends --recurse --no-recommends --no-suggests --no-conflicts --no-breaks --no-replaces --no-enhances --no-pre-depends {package_name}"
), stdout=subprocess.PIPE)
# Second pass: only grep interesting lines.
pass2 = subprocess.Popen(shlex.split(
r"grep '^\w'"
), stdin=pass1.stdout, stdout=subprocess.PIPE, encoding='utf-8')
stdout, stderr = pass2.communicate()
return stdout.strip().split("\n")
@staticmethod
def lib_to_deb(libraryname):
"""Uses system tools to identify the missing package."""
libsearch = subprocess.run(shlex.split(
f"sudo apt-file find -lx {libraryname}$"
), check=True, capture_output=True)
candidate = [ x for x in libsearch.stdout.decode('utf-8').split('\n') if 'lib' in x ][0]
return candidate