2022-03-20 23:57:45 +01:00
#!/usr/bin/env python3
import urllib . request
import loaih . versions as versions
from lxml import etree
import tempfile , os , sys , glob , subprocess , shutil
class Build ( object ) :
LANGSTD = [ ' ar ' , ' de ' , ' en-GB ' , ' es ' , ' fr ' , ' it ' , ' ja ' , ' ko ' , ' pt ' , ' pt-BR ' , ' ru ' , ' zh-CN ' , ' zh-TW ' ]
LANGBASIC = [ ' en-GB ' ]
def __init__ ( self , query , arch ) :
""" Build all versions that can be found in the indicated repo. """
self . query = query
2022-03-26 01:32:15 +01:00
self . queried_name = False if ' . ' in self . query else True
2022-03-20 23:57:45 +01:00
self . arch = arch
2022-03-21 01:10:17 +01:00
self . url = { }
2022-03-20 23:57:45 +01:00
self . language = ' basic '
self . offline_help = False
self . portable = False
self . updatable = True
self . storage_path = ' /srv/http/appimage.sys42.eu '
self . download_path = ' /var/tmp/downloads '
2022-03-21 00:18:09 +01:00
self . appimagefilename = { }
2022-03-26 01:32:15 +01:00
self . zsyncfilename = { }
2022-03-20 23:57:45 +01:00
# Getting versions and so on
v = versions . BuildVersion ( self . query )
2022-03-21 01:10:17 +01:00
# Creating a tempfile
self . builddir = tempfile . mkdtemp ( )
self . tarballs = { }
2022-03-22 01:24:55 +01:00
self . appname = ' LibreOffice ' if not self . query == ' daily ' and not self . query == ' prerelease ' else ' LibreOfficeDev '
2022-03-21 01:10:17 +01:00
self . version = v . version
self . url = v . basedirurl
self . built = False
2022-03-20 23:57:45 +01:00
# Building expected AppImageName
2022-03-22 01:24:55 +01:00
self . languagepart = " . "
2022-03-20 23:57:45 +01:00
if ' , ' in self . language :
2022-03-22 01:24:55 +01:00
self . languagepart + = self . language . replace ( ' , ' , ' - ' )
2022-03-21 00:48:42 +01:00
else :
2022-03-22 01:24:55 +01:00
self . languagepart + = self . language
2022-03-20 23:57:45 +01:00
2022-03-26 01:32:15 +01:00
self . helppart = ' .help ' if self . offline_help else ' '
self . appimagefilename [ u ' x86 ' ] = self . appname + ' - ' + self . version + self . languagepart + self . helppart + ' -x86.AppImage '
self . appimagefilename [ u ' x86_64 ' ] = self . appname + ' - ' + v . version + self . languagepart + self . helppart + ' -x86_64.AppImage '
2022-03-26 01:46:08 +01:00
myver = str . join ( ' . ' , self . version . split ( ' . ' ) [ 0 : 2 ] )
self . zsyncfilename [ u ' x86 ' ] = self . appname + ' - ' + myver + self . languagepart + self . helppart + ' -x86.AppImage.zsync '
self . zsyncfilename [ u ' x86_64 ' ] = self . appname + ' - ' + myver + self . languagepart + self . helppart + ' -x86_64.AppImage.zsync '
2022-03-20 23:57:45 +01:00
def check ( self , storage_path ) :
""" Checking if the requested AppImage has been already built. """
2022-03-22 02:04:25 +01:00
self . storage_path = storage_path
2022-03-22 01:24:55 +01:00
self . storage_path + = ( ' /daily ' if self . query == ' daily ' else ' ' )
self . storage_path + = ( ' /prerelease ' if self . query == ' prerelease ' else ' ' )
2022-03-22 02:04:25 +01:00
self . storage_path + = ( ' /portable ' if self . portable else ' ' )
2022-03-21 01:33:47 +01:00
# Incompatibilities - if portable and updatable are asked together,
# only portable will be built.
if self . portable and self . updatable :
print ( " Upgradable and portable options were required together. Building only portable. " )
self . updatable = False
2022-03-20 23:57:45 +01:00
2022-03-26 01:32:15 +01:00
if self . updatable and not self . queried_name :
# If the queried version was a numbered version, doesn't make sense
# to build an updatable version.
self . updatable = False
2022-03-20 23:57:45 +01:00
for arch in self . arch :
2022-03-21 01:10:17 +01:00
res = subprocess . check_output ( " find {path} -name ' {appimage} ' " . format (
path = self . storage_path ,
2022-03-26 01:32:15 +01:00
appimage = self . appimagefilename [ arch ]
2022-03-21 01:10:17 +01:00
) , shell = True ) . decode ( ' utf-8 ' ) . strip ( ' \n ' )
2022-03-20 23:57:45 +01:00
if len ( res ) > 1 :
self . built = True
if self . built :
print ( " The requested AppImage already exists on storage. I ' ll skip downloading, building and moving the results. " )
def download ( self , download_path ) :
""" Downloads the contents of the URL as it was a folder. """
if self . built :
return
# Let's start with defining which files are to be downloaded.
# Let's explore the remote folder.
self . download_path = download_path
2022-03-21 01:10:17 +01:00
for arch in self . arch :
# Checking if a valid path has been provided
if self . url [ arch ] == ' - ' :
print ( " No build has been provided for the requested AppImage for {arch} . Continue with other options. " . format ( arch = arch ) )
continue
contents = etree . HTML ( urllib . request . urlopen ( self . url [ arch ] ) . read ( ) ) . xpath ( " //td/a " )
2022-03-26 01:35:06 +01:00
self . tarballs [ arch ] = [ x . text for x in contents if x . text . endswith ( ' tar.gz ' ) and ' deb ' in x . text ]
2022-03-21 01:10:17 +01:00
tarballs = self . tarballs [ arch ]
maintarball = tarballs [ 0 ]
2022-03-21 01:11:17 +01:00
os . makedirs ( self . download_path , exist_ok = True )
2022-03-21 01:10:17 +01:00
os . chdir ( self . download_path )
for archive in tarballs :
2022-03-20 23:57:45 +01:00
# If the archive is already there, do not do anything.
2022-03-21 01:10:17 +01:00
if os . path . exists ( os . path . join ( self . download_path , archive ) ) :
2022-03-20 23:57:45 +01:00
print ( " Archive %s is already there! Sweet " % archive )
continue
# Download the archive
try :
2022-03-21 01:10:17 +01:00
urllib . request . urlretrieve ( self . url [ arch ] + archive , archive )
2022-03-20 23:57:45 +01:00
except :
print ( " Failed to download {archive} . " . format ( archive = archive ) )
print ( " Got %s . " % archive )
def build ( self ) :
""" Building all the versions. """
if self . built :
return
# We have 4 builds to do:
# * standard languages, no help
# * standard languages + offline help
# * all languages, no help
# * all languages + offline help
if self . portable and not ' portable ' in self . storage_path :
self . storage_path + = " /portable "
for arch in self . arch :
2022-03-21 01:10:17 +01:00
# Checking if a valid path has been provided
if self . url [ arch ] == ' - ' :
# User has been warned in download step.
continue
2022-03-20 23:57:45 +01:00
# Preparation tasks
self . appnamedir = os . path . join ( self . builddir , self . appname )
self . appimagedir = os . path . join ( self . builddir , self . appname , self . appname + ' .AppDir ' )
os . makedirs ( self . appimagedir , exist_ok = True )
# And then cd to the appname folder.
os . chdir ( self . appnamedir )
# Download appimagetool from github
appimagetoolurl = " https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool- {arch} .AppImage " . format ( arch = arch )
urllib . request . urlretrieve ( appimagetoolurl , ' appimagetool ' )
os . chmod ( ' appimagetool ' , 0o755 )
# Build the requested version.
2022-03-21 01:10:17 +01:00
self . __unpackbuild__ ( arch )
2022-03-20 23:57:45 +01:00
2022-03-21 01:10:17 +01:00
def __unpackbuild__ ( self , arch ) :
2022-03-20 23:57:45 +01:00
# We start by filtering out tarballs from the list
2022-03-21 01:10:17 +01:00
buildtarballs = [ self . tarballs [ arch ] [ 0 ] ]
2022-03-20 23:57:45 +01:00
# Let's process standard languages and append results to the
# buildtarball
if self . language == ' basic ' :
2022-03-21 01:10:17 +01:00
buildtarballs . extend ( [ x for x in self . tarballs [ arch ] if ' langpack_en-GB ' in x ] )
2022-03-20 23:57:45 +01:00
if self . offline_help :
2022-03-21 01:10:17 +01:00
buildtarballs . extend ( [ x for x in self . tarballs [ arch ] if ' helppack_en-GB ' in x ] )
2022-03-20 23:57:45 +01:00
if self . language == ' standard ' :
for lang in Build . LANGSTD :
2022-03-21 01:10:17 +01:00
buildtarballs . extend ( [ x for x in self . tarballs [ arch ] if ( ' langpack ' + lang ) in x ] )
2022-03-21 01:14:28 +01:00
if self . offline_help :
2022-03-21 01:10:17 +01:00
buildtarballs . extend ( [ x for x in self . tarballs [ arch ] if ( ' helppack ' + lang ) in x ] )
2022-03-20 23:57:45 +01:00
else :
# In any other cases, we build with all languages
2022-03-21 01:14:28 +01:00
if not self . offline_help :
2022-03-21 01:10:17 +01:00
buildtarballs . extend ( [ x for x in self . tarballs [ arch ] if ' langpack ' in x ] )
2022-03-20 23:57:45 +01:00
else :
# We need also all help. Let's replace buildtarball with the
# whole bunch
2022-03-21 01:10:17 +01:00
buildtarballs = self . tarballs [ arch ]
2022-03-20 23:57:45 +01:00
# Unpacking the tarballs
for archive in buildtarballs :
subprocess . run ( " tar xzf {folder} / {archive} " . format ( folder = self . download_path , archive = archive ) , shell = True )
os . chdir ( self . appnamedir )
2022-03-26 01:32:15 +01:00
2022-03-20 23:57:45 +01:00
os . makedirs ( self . appimagedir , exist_ok = True )
# At this point, let's decompress the deb packages
subprocess . run ( " find .. -iname ' *.deb ' -exec dpkg -x {} . \ ; " , shell = True , cwd = self . appimagedir )
2022-03-26 01:32:15 +01:00
2022-03-21 01:29:27 +01:00
if self . portable :
2022-03-21 01:37:03 +01:00
shortversion = str . join ( ' . ' , self . version . split ( ' . ' ) [ : 3 ] )
2022-03-21 01:39:56 +01:00
subprocess . run ( " find . -type f -iname ' bootstraprc ' -exec sed -i ' s|^UserInstallation=.*|UserInstallation= \ $SYSUSERCONFIG/libreoffice/ %s |g ' {} \ + " % shortversion , shell = True , cwd = self . appimagedir )
2022-03-21 01:29:27 +01:00
2022-03-20 23:57:45 +01:00
# Changing desktop file
subprocess . run ( " find . -iname startcenter.desktop -exec cp {} . \ ; " , shell = True , cwd = self . appimagedir )
2022-03-21 01:18:37 +01:00
subprocess . run ( " sed -i -e ' s:^Name=.*$:Name= %s : ' startcenter.desktop " % self . appname , shell = True , cwd = self . appimagedir )
2022-03-20 23:57:45 +01:00
subprocess . run ( " find . -name ' *startcenter.png ' -path ' *hicolor*48x48* ' -exec cp {} . \ ; " , shell = True , cwd = self . appimagedir )
# Find the name of the binary called in the desktop file.
binaryname = subprocess . check_output ( " awk ' BEGIN { FS = \" = \" } /^Exec/ { print $2; exit } ' startcenter.desktop | awk ' { print $1 } ' " , shell = True , cwd = self . appimagedir ) . decode ( ' utf-8 ' ) . strip ( ' \n ' )
bindir = os . path . join ( self . appimagedir , ' usr ' , ' bin ' )
os . makedirs ( bindir , exist_ok = True )
subprocess . run ( " find ../../opt -iname soffice -path ' *program* ' -exec ln -sf {} ./ %s \ ; " % binaryname , shell = True , cwd = bindir )
# Download AppRun from github
apprunurl = " https://github.com/AppImage/AppImageKit/releases/download/continuous/AppRun- {arch} " . format ( arch = arch )
dest = os . path . join ( self . appimagedir , ' AppRun ' )
urllib . request . urlretrieve ( apprunurl , dest )
os . chmod ( dest , 0o755 )
# Setting app version
2022-03-21 01:10:17 +01:00
appversion = self . version + ' . ' + ( self . language if not ' , ' in self . language else self . language . replace ( ' , ' , ' - ' ) )
2022-03-20 23:57:45 +01:00
if self . offline_help :
appversion + = ' .help '
# Building app
2022-03-26 01:32:15 +01:00
if self . updatable and self . queried_name :
# Updatable make sense only for generic images for fresh, still,
# daily. If a request was for a specific version, I'd not build an
# updatable version.
# zsync name was generated already
subprocess . run ( " VERSION= {version} ./appimagetool -u ' zsync| {zsync} ' -v ./ {appname} .AppDir/ " . format ( version = appversion , zsync = self . zsyncfilename [ arch ] , appname = self . appname ) , shell = True )
2022-03-20 23:57:45 +01:00
else :
subprocess . run ( " VERSION= {version} ./appimagetool -v ./ {appname} .AppDir/ " . format ( version = appversion , appname = self . appname ) , shell = True )
print ( " Built AppImage version {version} " . format ( version = appversion ) )
# Cleanup phase, before new run.
for deb in glob . glob ( self . appnamedir + ' /*.deb ' ) :
os . remove ( deb )
subprocess . run ( " find . -type d -maxdepth 1 -exec rm -rf {} \ + " , shell = True )
def checksums ( self ) :
""" Create checksums of the built versions. """
if self . built :
return
os . chdir ( self . appnamedir )
for appimage in glob . glob ( ' *.AppImage* ' ) :
# See if a checksum already exist
if not os . path . exists ( appimage + ' .md5 ' ) :
subprocess . run ( " md5sum {appimage} > {appimage} .md5 " . format ( appimage = appimage ) , shell = True )
2022-03-21 00:38:07 +01:00
def move ( self ) :
2022-03-20 23:57:45 +01:00
""" Moves built versions to definitive storage. """
if self . built :
return
os . chdir ( self . appnamedir )
subprocess . run ( " find . -iname ' *.AppImage* ' -exec cp {} %s \ ; " % self . storage_path , shell = True )
def __del__ ( self ) :
""" Destructor """
# Cleaning up build directory
shutil . rmtree ( self . builddir )