2022-03-20 23:57:45 +01:00
#!/usr/bin/env python3
import urllib . request
import loaih . versions as versions
from lxml import etree
2022-04-08 00:56:28 +02:00
import tempfile , os , sys , glob , subprocess , shutil , re , shlex
2022-03-20 23:57:45 +01:00
class Build ( object ) :
LANGSTD = [ ' ar ' , ' de ' , ' en-GB ' , ' es ' , ' fr ' , ' it ' , ' ja ' , ' ko ' , ' pt ' , ' pt-BR ' , ' ru ' , ' zh-CN ' , ' zh-TW ' ]
LANGBASIC = [ ' en-GB ' ]
2022-03-31 01:19:51 +02:00
ARCHSTD = [ u ' x86 ' , u ' x86_64 ' ]
2022-03-20 23:57:45 +01:00
def __init__ ( self , query , arch ) :
""" Build all versions that can be found in the indicated repo. """
self . query = query
self . arch = arch
2022-04-08 00:56:28 +02:00
# Getting versions and so on
v = versions . BuildVersion ( self . query )
self . version = v . version
2022-04-08 01:45:42 +02:00
print ( f " Debug { self . version } " )
2022-04-08 00:56:28 +02:00
self . short_version = str . join ( ' . ' , self . version . split ( ' . ' ) [ 0 : 2 ] )
self . branch_version = None
if not ' . ' in self . query :
self . branch_version = self . query
self . url = v . basedirurl
# Other default values
2022-03-20 23:57:45 +01:00
self . language = ' basic '
self . offline_help = False
self . portable = False
self . updatable = True
2022-04-08 00:56:28 +02:00
self . sign = True
self . storage_path = ' /mnt/appimage '
2022-03-20 23:57:45 +01:00
self . download_path = ' /var/tmp/downloads '
2022-03-31 01:50:15 +02:00
# Specific build version
2022-03-26 02:10:25 +01:00
self . appversion = ' '
2022-03-31 01:50:15 +02:00
self . appimagefilename = { }
2022-04-08 00:56:28 +02:00
self . zsyncfilename = { }
2022-03-20 23:57:45 +01:00
2022-03-21 01:10:17 +01:00
# Creating a tempfile
self . builddir = tempfile . mkdtemp ( )
self . tarballs = { }
2022-03-31 01:19:51 +02:00
self . built = { u ' x86 ' : False , u ' x86_64 ' : False }
2022-03-30 00:07:43 +02:00
# Preparing the default for the relative path on the storage for
# different versions.
# The path will evaluated as part of the check() function, as it is
# understood the storage_path can be changed before that phase.
self . relative_path = [ ]
self . full_path = ' '
2022-04-08 22:59:59 +02:00
self . baseurl = ' '
2022-03-21 01:10:17 +01:00
2022-03-31 09:38:00 +02:00
2022-03-31 02:12:46 +02:00
def calculate ( self ) :
""" Calculate exclusions and other variables. """
2022-04-08 00:56:28 +02:00
# AppName
self . appname = ' LibreOffice ' if not self . query == ' daily ' and not self . query == ' prerelease ' else ' LibreOfficeDev '
2022-03-31 02:12:46 +02:00
2022-04-08 00:56:28 +02:00
# Calculating languagepart
2022-03-31 01:50:15 +02:00
self . languagepart = " . "
if ' , ' in self . language :
self . languagepart + = self . language . replace ( ' , ' , ' - ' )
else :
self . languagepart + = self . language
2022-04-08 00:56:28 +02:00
# Calculating help part
2022-03-31 01:50:15 +02:00
self . helppart = ' .help ' if self . offline_help else ' '
2022-04-08 00:56:28 +02:00
# Building the required names
2022-03-31 01:51:37 +02:00
for arch in Build . ARCHSTD :
2022-04-08 01:02:39 +02:00
self . appimagefilename [ arch ] = self . __gen_appimagefilename__ ( self . version , arch )
2022-04-08 00:56:28 +02:00
self . zsyncfilename [ arch ] = self . appimagefilename [ arch ] + ' .zsync '
2022-03-31 01:50:15 +02:00
2022-04-08 00:56:28 +02:00
# Mandate to the private function to calculate the full_path available
# for the storage and the checks.
self . __calculate_full_path__ ( )
2022-03-26 02:42:53 +01:00
2022-03-30 00:07:43 +02:00
2022-04-08 00:56:28 +02:00
def __gen_appimagefilename__ ( self , version , arch ) :
""" Generalize the construction of the name of the app. """
2022-04-08 02:01:39 +02:00
self . appversion = version + self . languagepart + self . helppart
return self . appname + f ' - { self . appversion } - { arch } .AppImage '
2022-03-30 00:07:43 +02:00
2022-03-31 09:38:00 +02:00
2022-03-30 00:07:43 +02:00
def __calculate_full_path__ ( self ) :
""" Calculate relative path of the build, based on internal other variables. """
if len ( self . relative_path ) == 0 :
if self . query == ' daily ' :
self . relative_path . append ( ' daily ' )
2022-04-08 01:41:17 +02:00
elif self . query == ' primageerelease ' :
2022-03-30 00:07:43 +02:00
self . relative_path . append ( ' prerelease ' )
2022-03-20 23:57:45 +01:00
2022-03-30 00:07:43 +02:00
# Not the same check, an additional one
if self . portable :
self . relative_path . append ( ' portable ' )
fullpath_arr = self . storage_path . split ( ' / ' )
# Joining relative path only if it is not null
if len ( self . relative_path ) > 0 :
2022-03-30 00:12:15 +02:00
fullpath_arr . extend ( self . relative_path )
2022-03-30 00:07:43 +02:00
self . full_path = re . sub ( r " /+ " , ' / ' , str . join ( ' / ' , fullpath_arr ) )
2022-03-20 23:57:45 +01:00
2022-03-31 09:38:00 +02:00
2022-04-08 00:56:28 +02:00
def check ( self ) :
""" Checking if the requested AppImage has been already built. """
if not len ( self . appimagefilename ) == 2 :
self . calculate ( )
for arch in self . arch :
print ( f " Searching for { self . appimagefilename [ arch ] } " )
res = subprocess . run ( shlex . split ( f " find { self . full_path } -name { self . appimagefilename [ arch ] } ' " ) , capture_output = True , env = { " LC_ALL " : " C " } , text = True , encoding = ' utf-8 ' )
if " No such file or directory " in res . stderr :
# Folder is not existent: so the version was not built
# Build stays false, and we go to the next arch
continue
2022-04-08 01:56:04 +02:00
if res . stdout and len ( res . stdout . strip ( " \n " ) ) > 0 :
2022-04-08 00:56:28 +02:00
# All good, the command was executed fine.
2022-04-08 01:56:04 +02:00
print ( f " Build for { self . version } found. " )
self . built [ arch ] = True
2022-04-08 00:56:28 +02:00
if self . built [ arch ] :
print ( f " The requested AppImage already exists on storage for { arch } . I ' ll skip downloading, building and moving the results. " )
2022-03-31 02:12:46 +02:00
def download ( self ) :
2022-03-20 23:57:45 +01:00
""" Downloads the contents of the URL as it was a folder. """
2022-04-08 00:56:28 +02:00
print ( f " Started downloads for { self . version } . Please wait. " )
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 ] == ' - ' :
2022-04-08 00:56:28 +02:00
print ( f " No build has been provided for the requested AppImage for { arch } . Continue with other options. " )
2022-03-30 00:07:43 +02:00
# Faking already built it so to skip other checks.
self . built [ arch ] = True
continue
if self . built [ arch ] :
2022-04-08 00:56:28 +02:00
print ( f " A build for { arch } was already found. Skipping specific packages. " )
2022-03-21 01:10:17 +01:00
continue
2022-04-08 00:56:28 +02:00
# Identifying downloads
2022-03-21 01:10:17 +01:00
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-04-08 00:56:28 +02:00
# Create and change directory to the download location
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-04-08 00:56:28 +02:00
if os . path . exists ( archive ) :
2022-03-20 23:57:45 +01:00
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 :
2022-04-08 00:56:28 +02:00
print ( f " Failed to download { archive } . " )
2022-03-20 23:57:45 +01:00
2022-04-08 00:56:28 +02:00
print ( f " Finished downloads for { self . version } . " )
2022-03-31 09:38:00 +02:00
2022-03-20 23:57:45 +01:00
def build ( self ) :
""" Building all the versions. """
for arch in self . arch :
2022-03-30 00:07:43 +02:00
if self . built [ arch ] :
# Already built for arch or path not available. User has already been warned.
2022-03-21 01:10:17 +01:00
continue
2022-03-20 23:57:45 +01:00
# Preparation tasks
self . appnamedir = os . path . join ( self . builddir , self . appname )
2022-04-08 01:05:14 +02:00
os . makedirs ( self . appnamedir , exist_ok = True )
2022-03-20 23:57:45 +01:00
# And then cd to the appname folder.
os . chdir ( self . appnamedir )
# Download appimagetool from github
2022-04-08 00:56:28 +02:00
appimagetoolurl = f " https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool- { arch } .AppImage "
2022-03-20 23:57:45 +01:00
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-31 09:38:00 +02:00
2022-04-08 00:56:28 +02: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 ' :
if self . offline_help :
2022-03-30 00:25:42 +02:00
buildtarballs . extend ( [ x for x in self . tarballs [ arch ] if ' pack_en-GB ' in x ] )
else :
buildtarballs . extend ( [ x for x in self . tarballs [ arch ] if ' langpack_en-GB ' in x ] )
elif self . language == ' standard ' :
2022-03-20 23:57:45 +01:00
for lang in Build . LANGSTD :
2022-03-21 01:14:28 +01:00
if self . offline_help :
2022-03-30 01:10:36 +02:00
buildtarballs . extend ( [ x for x in self . tarballs [ arch ] if ( ' pack_ ' + lang ) in x ] )
2022-03-30 00:25:42 +02:00
else :
2022-03-30 01:10:36 +02:00
buildtarballs . extend ( [ x for x in self . tarballs [ arch ] if ( ' langpack_ ' + lang ) in x ] )
2022-03-30 00:42:31 +02:00
elif self . language == ' full ' :
if self . offline_help :
2022-03-20 23:57:45 +01:00
# 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-30 00:42:31 +02:00
else :
buildtarballs . extend ( [ x for x in self . tarballs [ arch ] if ' langpack ' in x ] )
else :
# Looping for each language in self.language
for lang in self . language . split ( " , " ) :
if self . offline_help :
buildtarballs . extend ( [ x for x in self . tarballs [ arch ] if ( ' pack ' + lang ) in x ] )
else :
buildtarballs . extend ( [ x for x in self . tarballs [ arch ] if ( ' langpack ' + lang ) in x ] )
2022-03-20 23:57:45 +01:00
2022-04-08 00:56:28 +02:00
os . chdir ( self . appnamedir )
2022-03-20 23:57:45 +01:00
# Unpacking the tarballs
for archive in buildtarballs :
2022-04-08 00:56:28 +02:00
subprocess . run ( shlex . split ( f " tar xzf { self . download_path } / { archive } " ) )
2022-03-26 01:32:15 +01:00
2022-04-08 00:56:28 +02:00
# create appimagedir
self . appimagedir = os . path . join ( self . builddir , self . appname , self . appname + ' .AppDir ' )
2022-03-20 23:57:45 +01:00
os . makedirs ( self . appimagedir , exist_ok = True )
2022-04-08 00:56:28 +02:00
2022-03-20 23:57:45 +01:00
# At this point, let's decompress the deb packages
2022-04-08 00:56:28 +02:00
subprocess . run ( shlex . split ( " find .. -iname ' *.deb ' -exec dpkg -x {} . \ ; " ) , cwd = self . appimagedir )
2022-03-26 01:32:15 +01:00
2022-03-21 01:29:27 +01:00
if self . portable :
2022-04-08 00:56:28 +02:00
subprocess . run ( shlex . split ( " find . -type f -iname ' bootstraprc ' -exec sed -i ' s|^UserInstallation=.*|UserInstallation= \ $SYSUSERCONFIG/libreoffice/ %s |g ' {} \ + " % self . short_version ) , cwd = self . appimagedir )
2022-03-21 01:29:27 +01:00
2022-03-20 23:57:45 +01:00
# Changing desktop file
2022-04-08 00:56:28 +02:00
subprocess . run ( shlex . split ( " find . -iname startcenter.desktop -exec cp {} . \ ; " ) , cwd = self . appimagedir )
2022-04-09 00:06:29 +02:00
subprocess . run ( shlex . split ( " sed --in-place ' s:^Name=.*$:Name= %s : ' startcenter.desktop > startcenter.desktop " % self . appname ) , cwd = self . appimagedir )
2022-03-20 23:57:45 +01:00
2022-04-08 00:56:28 +02:00
subprocess . run ( shlex . split ( " find . -name ' *startcenter.png ' -path ' *hicolor*48x48* ' -exec cp {} . \ ; " ) , cwd = self . appimagedir )
2022-03-20 23:57:45 +01:00
# Find the name of the binary called in the desktop file.
2022-04-08 01:28:18 +02:00
binaryname = ' '
with open ( os . path . join ( self . appimagedir , ' startcenter.desktop ' ) , ' r ' ) as d :
a = d . readlines ( )
for line in a :
if re . match ( r ' ^Exec ' , line ) :
binaryname = line . split ( ' = ' ) [ - 1 ] . split ( ' ' ) [ 0 ]
# Esci al primo match
break
#binary_exec = subprocess.run(shlex.split(r"awk 'BEGIN { FS = \"=\" } /^Exec/ { print $2; exit }' startcenter.desktop | awk '{ print $1 }'"), cwd=self.appimagedir, text=True, encoding='utf-8')
#binaryname = binary_exec.stdout.strip("\n")
2022-03-20 23:57:45 +01:00
bindir = os . path . join ( self . appimagedir , ' usr ' , ' bin ' )
os . makedirs ( bindir , exist_ok = True )
2022-04-08 00:56:28 +02:00
subprocess . run ( shlex . split ( " find ../../opt -iname soffice -path ' *program* ' -exec ln -sf {} ./ %s \ ; " % binaryname ) , cwd = bindir )
2022-03-20 23:57:45 +01:00
# Download AppRun from github
2022-04-08 00:56:28 +02:00
apprunurl = f " https://github.com/AppImage/AppImageKit/releases/download/continuous/AppRun- { arch } "
2022-03-20 23:57:45 +01:00
dest = os . path . join ( self . appimagedir , ' AppRun ' )
urllib . request . urlretrieve ( apprunurl , dest )
os . chmod ( dest , 0o755 )
2022-03-31 09:38:00 +02:00
# Dealing with extra options
buildopts = [ ]
if self . sign :
buildopts . append ( ' --sign ' )
2022-03-20 23:57:45 +01:00
2022-04-08 00:56:28 +02:00
# adding zsync build if updatable
if self . updatable :
buildopts . append ( f " -u ' zsync| { self . zsyncfilename [ arch ] } ' " )
2022-03-20 23:57:45 +01:00
2022-04-08 00:56:28 +02:00
buildopts_str = str . join ( ' ' , buildopts )
# Build the number-specific build
2022-04-08 01:41:17 +02:00
subprocess . run ( shlex . split ( f " { self . appnamedir } /appimagetool { buildopts_str } -v ./ { self . appname } .AppDir/ " ) , env = { " VERSION " : self . appversion } )
2022-04-08 00:56:28 +02:00
print ( f " Built AppImage version { self . appversion } " )
2022-03-20 23:57:45 +01:00
# Cleanup phase, before new run.
for deb in glob . glob ( self . appnamedir + ' /*.deb ' ) :
os . remove ( deb )
2022-04-08 00:56:28 +02:00
subprocess . run ( shlex . split ( " find . -mindepth 1 -maxdepth 1 -type d -exec rm -rf {} \ + " ) )
2022-03-20 23:57:45 +01:00
2022-03-31 09:38:00 +02:00
2022-03-20 23:57:45 +01:00
def checksums ( self ) :
""" Create checksums of the built versions. """
2022-04-08 00:56:28 +02:00
# Skip checksum if initally the build was already found in the storage directory
2022-03-30 00:07:43 +02:00
if all ( self . built . values ( ) ) :
2022-04-08 00:56:28 +02:00
return
2022-03-30 00:07:43 +02:00
2022-03-20 23:57:45 +01:00
os . chdir ( self . appnamedir )
2022-04-08 00:56:28 +02:00
for arch in self . arch :
for item in [ self . appimagefilename [ arch ] , self . zsyncfilename [ arch ] ] :
# For any built arch, find out if a file exist.
2022-04-08 01:41:17 +02:00
if not os . path . exists ( f " { item } .md5 " ) :
2022-04-08 00:56:28 +02:00
# Build checksum
2022-04-08 01:56:04 +02:00
checksum = subprocess . run ( shlex . split ( f " md5sum { item } " ) , capture_output = True , text = True , encoding = ' utf-8 ' )
if checksum . stdout :
with open ( f " { item } .md5 " , ' w+ ' ) as f :
f . write ( checksum . stdout )
2022-03-20 23:57:45 +01:00
2022-03-30 00:07:43 +02:00
def publish ( self ) :
2022-03-20 23:57:45 +01:00
""" Moves built versions to definitive storage. """
2022-03-30 00:07:43 +02:00
if all ( self . built . values ( ) ) :
# All files are already present in the full_path
2022-03-20 23:57:45 +01:00
return
os . chdir ( self . appnamedir )
2022-03-30 00:07:43 +02:00
# Forcing creation of subfolders, in case there is a new build
os . makedirs ( self . full_path , exist_ok = True )
2022-04-08 00:56:28 +02:00
for file in glob . glob ( " *.AppImage* " ) :
2022-04-08 01:41:17 +02:00
subprocess . run ( shlex . split ( f " cp -f { file } { self . full_path } " ) )
2022-04-08 00:56:28 +02:00
def generalize_and_link ( self ) :
""" Creates the needed generalized files if needed. """
# If called with a pointed version, no generalize and link necessary.
if not self . branch_version :
return
2022-04-08 23:14:47 +02:00
appimagefilename = { }
zsyncfilename = { }
2022-04-08 00:56:28 +02:00
# Creating versions for short version and query text
versions = [ self . short_version , self . branch_version ]
for arch in Build . ARCHSTD :
2022-04-08 22:59:59 +02:00
os . chdir ( self . full_path )
2022-04-08 00:56:28 +02:00
# if the appimage for the reported arch is not found, skip to next
# arch
if not os . path . exists ( self . appimagefilename [ arch ] ) :
continue
# Doing it both for short_name and for branchname
for version in versions :
appimagefilename [ arch ] = self . appname + ' - ' + version + self . languagepart + self . helppart + f ' - { arch } .AppImage '
zsyncfilename [ arch ] = appimagefilename [ arch ] + ' .zsync '
# Create the symlink
2022-04-08 22:59:59 +02:00
print ( f " Creating { appimagefilename [ arch ] } and checksums. " )
2022-04-08 23:26:50 +02:00
if os . path . exists ( appimagefilename [ arch ] ) :
os . unlink ( appimagefilename [ arch ] )
2022-04-08 00:56:28 +02:00
os . symlink ( self . appimagefilename [ arch ] , appimagefilename [ arch ] )
# Create the checksum for the AppImage
2022-04-08 22:59:59 +02:00
subprocess . run ( shlex . split ( f " md5sum { appimagefilename [ arch ] } > { appimagefilename [ arch ] } .md5 " ) )
2022-04-08 00:56:28 +02:00
# Do not continue if no zsync file is provided.
if not self . updatable :
continue
2022-04-08 22:59:59 +02:00
print ( f " Creating zsync file for version { version } . " )
2022-04-08 23:26:50 +02:00
if os . path . exists ( zsyncfilename [ arch ] ) :
os . unlink ( zsyncfilename [ arch ] )
2022-04-08 22:59:59 +02:00
shutil . copyfile ( self . zsyncfilename [ arch ] , zsyncfilename [ arch ] )
2022-04-08 00:56:28 +02:00
# Editing the zsyncfile
2022-04-09 00:06:29 +02:00
subprocess . run ( shlex . split ( f " sed --in-place ' s/^Filename:.*$/Filename: { appimagefilename [ arch ] } / ' { zsyncfilename [ arch ] } " ) )
2022-03-20 23:57:45 +01:00
2022-03-31 09:38:00 +02:00
2022-03-20 23:57:45 +01:00
def __del__ ( self ) :
""" Destructor """
# Cleaning up build directory
shutil . rmtree ( self . builddir )