akvirtualcamera/ports/deploy/deploy_posix.py

487 lines
17 KiB
Python
Raw Normal View History

2020-06-05 22:09:17 +00:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Webcamoid, webcam capture application.
# Copyright (C) 2017 Gonzalo Exequiel Pedone
#
# Webcamoid is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Webcamoid is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Webcamoid. If not, see <http://www.gnu.org/licenses/>.
#
# Web-Site: http://webcamoid.github.io/
import configparser
import math
import os
import platform
import subprocess # nosec
import sys
import tarfile
import threading
2020-11-18 19:33:36 +00:00
from WebcamoidDeployTools import DTDeployBase
from WebcamoidDeployTools import DTQt5
from WebcamoidDeployTools import DTBinaryElf
2020-06-05 22:09:17 +00:00
2020-11-18 19:33:36 +00:00
class Deploy(DTDeployBase.DeployBase, DTQt5.Qt5Tools):
2020-06-05 22:09:17 +00:00
def __init__(self):
super().__init__()
2020-11-18 19:33:36 +00:00
rootDir = os.path.normpath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../..'))
self.setRootDir(rootDir)
2020-06-05 22:09:17 +00:00
self.installDir = os.path.join(self.buildDir, 'ports/deploy/temp_priv')
self.pkgsDir = os.path.join(self.buildDir, 'ports/deploy/packages_auto', sys.platform)
2020-06-17 22:52:39 +00:00
self.detectQt(os.path.join(self.buildDir, 'Manager'))
2020-06-05 22:09:17 +00:00
self.rootInstallDir = os.path.join(self.installDir, self.qmakeQuery(var='QT_INSTALL_PREFIX')[1:])
self.binaryInstallDir = os.path.join(self.rootInstallDir, 'bin')
self.mainBinary = os.path.join(self.binaryInstallDir, 'webcamoid')
self.programName = os.path.basename(self.mainBinary)
self.programVersion = self.detectVersion(os.path.join(self.rootDir, 'commons.pri'))
self.detectMake()
xspec = self.qmakeQuery(var='QMAKE_XSPEC')
if 'win32' in xspec:
self.targetSystem = 'posix_windows'
elif 'android' in xspec:
self.targetSystem = 'android'
2020-11-18 19:33:36 +00:00
self.binarySolver = DTBinaryElf.ElfBinaryTools()
self.binarySolver.readExcludes(os.name, sys.platform)
2020-06-05 22:09:17 +00:00
self.packageConfig = os.path.join(self.rootDir, 'ports/deploy/package_info.conf')
self.dependencies = []
self.installerConfig = os.path.join(self.installDir, 'installer/config')
self.installerPackages = os.path.join(self.installDir, 'installer/packages')
self.installerIconSize = 128
self.appIcon = os.path.join(self.installDir,
'usr/share/icons/hicolor/{1}x{1}/apps/{0}.png'.format(self.programName,
self.installerIconSize))
self.licenseFile = os.path.join(self.rootDir, 'COPYING')
self.installerRunProgram = '@TargetDir@/' + self.programName + '.sh'
self.installerTargetDir = '@HomeDir@/' + self.programName
self.installerScript = os.path.join(self.rootDir, 'ports/deploy/installscript.posix.qs')
self.changeLog = os.path.join(self.rootDir, 'ChangeLog')
self.outPackage = os.path.join(self.pkgsDir,
2020-11-18 20:28:42 +00:00
'{}-installer-{}-{}.run'.format(self.programName,
self.programVersion,
platform.machine()))
2020-06-05 22:09:17 +00:00
def detectAppImage(self):
if 'APPIMAGETOOL' in os.environ:
return os.environ['APPIMAGETOOL']
appimagetool = self.whereBin('appimagetool')
if len(appimagetool) > 0:
return appimagetool
if self.targetArch == '32bit':
return self.whereBin('appimagetool-i686.AppImage')
return self.whereBin('appimagetool-x86_64.AppImage')
def solvedepsLibs(self):
qtLibsPath = self.qmakeQuery(var='QT_INSTALL_LIBS')
self.binarySolver.ldLibraryPath.append(qtLibsPath)
deps = sorted(self.binarySolver.scanDependencies(self.installDir))
for dep in deps:
depPath = os.path.join(self.libInstallDir, os.path.basename(dep))
if dep != depPath:
print(' {} -> {}'.format(dep, depPath))
self.copy(dep, depPath, True)
self.dependencies.append(dep)
def prepare(self):
print('Executing make install')
self.makeInstall(self.buildDir, self.installDir)
self.detectTargetArch()
self.appImage = self.detectAppImage()
print('Copying Qml modules\n')
self.solvedepsQml()
print('\nCopying required plugins\n')
self.solvedepsPlugins()
print('\nCopying required libs\n')
self.solvedepsLibs()
print('\nWritting qt.conf file')
self.writeQtConf()
print('Stripping symbols')
self.binarySolver.stripSymbols(self.installDir)
print('Resetting file permissions')
self.binarySolver.resetFilePermissions(self.rootInstallDir,
self.binaryInstallDir)
print('Writting launcher file')
self.createLauncher()
print('\nWritting build system information\n')
self.writeBuildInfo()
def searchPackageFor(self, path):
os.environ['LC_ALL'] = 'C'
pacman = self.whereBin('pacman')
if len(pacman) > 0:
process = subprocess.Popen([pacman, '-Qo', path], # nosec
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, _ = process.communicate()
if process.returncode != 0:
return ''
info = stdout.decode(sys.getdefaultencoding()).split(' ')
if len(info) < 2:
return ''
package, version = info[-2:]
return ' '.join([package.strip(), version.strip()])
dpkg = self.whereBin('dpkg')
if len(dpkg) > 0:
process = subprocess.Popen([dpkg, '-S', path], # nosec
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, _ = process.communicate()
if process.returncode != 0:
return ''
package = stdout.split(b':')[0].decode(sys.getdefaultencoding()).strip()
process = subprocess.Popen([dpkg, '-s', package], # nosec
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, _ = process.communicate()
if process.returncode != 0:
return ''
for line in stdout.decode(sys.getdefaultencoding()).split('\n'):
line = line.strip()
if line.startswith('Version:'):
return ' '.join([package, line.split()[1].strip()])
return ''
rpm = self.whereBin('rpm')
if len(rpm) > 0:
process = subprocess.Popen([rpm, '-qf', path], # nosec
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, _ = process.communicate()
if process.returncode != 0:
return ''
return stdout.decode(sys.getdefaultencoding()).strip()
pkg = self.whereBin('pkg')
if len(pkg) > 0:
process = subprocess.Popen([pkg, 'which', '-q', path], # nosec
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, _ = process.communicate()
if process.returncode != 0:
return ''
return stdout.decode(sys.getdefaultencoding()).strip()
return ''
def commitHash(self):
try:
process = subprocess.Popen(['git', 'rev-parse', 'HEAD'], # nosec
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
cwd=self.rootDir)
stdout, _ = process.communicate()
if process.returncode != 0:
return ''
return stdout.decode(sys.getdefaultencoding()).strip()
except:
return ''
@staticmethod
def sysInfo():
info = ''
for f in os.listdir('/etc'):
if f.endswith('-release'):
with open(os.path.join('/etc' , f)) as releaseFile:
info += releaseFile.read()
if len(info) < 1:
info = ' '.join(platform.uname())
return info
def writeBuildInfo(self):
shareDir = os.path.join(self.rootInstallDir, 'share')
try:
os.makedirs(self.pkgsDir)
except:
pass
depsInfoFile = os.path.join(shareDir, 'build-info.txt')
if not os.path.exists(shareDir):
os.makedirs(shareDir)
# Write repository info.
with open(depsInfoFile, 'w') as f:
commitHash = self.commitHash()
if len(commitHash) < 1:
commitHash = 'Unknown'
print(' Commit hash: ' + commitHash)
f.write('Commit hash: ' + commitHash + '\n')
buildLogUrl = ''
if 'TRAVIS_BUILD_WEB_URL' in os.environ:
buildLogUrl = os.environ['TRAVIS_BUILD_WEB_URL']
elif 'APPVEYOR_ACCOUNT_NAME' in os.environ and 'APPVEYOR_PROJECT_NAME' in os.environ and 'APPVEYOR_JOB_ID' in os.environ:
buildLogUrl = 'https://ci.appveyor.com/project/{}/{}/build/job/{}'.format(os.environ['APPVEYOR_ACCOUNT_NAME'],
os.environ['APPVEYOR_PROJECT_SLUG'],
os.environ['APPVEYOR_JOB_ID'])
if len(buildLogUrl) > 0:
print(' Build log URL: ' + buildLogUrl)
f.write('Build log URL: ' + buildLogUrl + '\n')
print()
f.write('\n')
# Write host info.
info = self.sysInfo()
with open(depsInfoFile, 'a') as f:
for line in info.split('\n'):
if len(line) > 0:
print(' ' + line)
f.write(line + '\n')
print()
f.write('\n')
# Write binary dependencies info.
packages = set()
for dep in self.dependencies:
packageInfo = self.searchPackageFor(dep)
if len(packageInfo) > 0:
packages.add(packageInfo)
packages = sorted(packages)
with open(depsInfoFile, 'a') as f:
for packge in packages:
print(' ' + packge)
f.write(packge + '\n')
def createLauncher(self):
path = os.path.join(self.rootInstallDir, self.programName) + '.sh'
libDir = self.qmakeQuery(var='QT_INSTALL_LIBS') \
.replace(self.qmakeQuery(var='QT_INSTALL_PREFIX'),
'${ROOTDIR}')
with open(path, 'w') as launcher:
launcher.write('#!/bin/sh\n')
launcher.write('\n')
launcher.write('path=$(realpath "$0")\n')
launcher.write('ROOTDIR=$(dirname "$path")\n')
launcher.write('export PATH="${ROOTDIR}/bin:$PATH"\n')
launcher.write('export LD_LIBRARY_PATH="{}:$LD_LIBRARY_PATH"\n'.format(libDir))
launcher.write('#export QT_DEBUG_PLUGINS=1\n')
launcher.write('{} "$@"\n'.format(self.programName))
os.chmod(path, 0o744)
@staticmethod
def hrSize(size):
i = int(math.log(size) // math.log(1024))
if i < 1:
return '{} B'.format(size)
units = ['KiB', 'MiB', 'GiB', 'TiB']
sizeKiB = size / (1024 ** i)
return '{:.2f} {}'.format(sizeKiB, units[i - 1])
def printPackageInfo(self, path):
if os.path.exists(path):
print(' ',
os.path.basename(path),
self.hrSize(os.path.getsize(path)))
print(' sha256sum:', Deploy.sha256sum(path))
else:
print(' ',
os.path.basename(path),
'FAILED')
def createPortable(self, mutex):
packagePath = \
os.path.join(self.pkgsDir,
'{}-portable-{}-{}.tar.xz'.format(self.programName,
self.programVersion,
platform.machine()))
if not os.path.exists(self.pkgsDir):
os.makedirs(self.pkgsDir)
with tarfile.open(packagePath, 'w:xz') as tar:
tar.add(self.rootInstallDir, self.programName)
mutex.acquire()
print('Created portable package:')
self.printPackageInfo(packagePath)
mutex.release()
def createAppInstaller(self, mutex):
packagePath = self.createInstaller()
if not packagePath:
return
mutex.acquire()
print('Created installable package:')
self.printPackageInfo(self.outPackage)
mutex.release()
def createAppImage(self, mutex):
if not os.path.exists(self.appImage):
return
appDir = \
os.path.join(self.installDir,
'{}-{}-{}.AppDir'.format(self.programName,
self.programVersion,
platform.machine()))
usrDir = os.path.join(appDir, 'usr')
if not os.path.exists(usrDir):
os.makedirs(usrDir)
self.copy(self.rootInstallDir, usrDir)
launcher = os.path.join(appDir, 'AppRun')
if not os.path.exists(launcher):
os.symlink('./usr/webcamoid.sh', launcher)
desktopFile = os.path.join(appDir, 'webcamoid.desktop')
if os.path.exists(desktopFile):
os.remove(desktopFile)
self.copy(os.path.join(usrDir, 'share/applications/webcamoid.desktop'), desktopFile)
config = configparser.ConfigParser()
config.optionxform=str
config.read(desktopFile, 'utf-8')
config['Desktop Entry']['Exec'] = 'webcamoid.sh'
del config['Desktop Entry']['Keywords']
with open(desktopFile, 'w', encoding='utf-8') as configFile:
config.write(configFile, space_around_delimiters=False)
icons = {'webcamoid.png': '256x256', '.DirIcon': '48x48'}
for icon in icons:
iconPath = os.path.join(appDir, icon)
if not os.path.exists(icon):
sourceIcon = os.path.join(usrDir,
'share/icons/hicolor/{}/apps/webcamoid.png'.format(icons[icon]))
self.copy(sourceIcon, iconPath)
# Remove old file
packagePath = \
os.path.join(self.pkgsDir,
'{}-{}-{}.AppImage'.format(self.programName,
self.programVersion,
platform.machine()))
if not os.path.exists(self.pkgsDir):
os.makedirs(self.pkgsDir)
if os.path.exists(packagePath):
os.remove(packagePath)
penv = os.environ.copy()
penv['ARCH'] = platform.machine()
libPaths = []
if 'LD_LIBRARY_PATH' in penv:
libPaths = penv['LD_LIBRARY_PATH'].split(':')
libDir = os.path.relpath(self.qmakeQuery(var='QT_INSTALL_LIBS'),
self.qmakeQuery(var='QT_INSTALL_PREFIX'))
penv['LD_LIBRARY_PATH'] = \
':'.join([os.path.abspath(os.path.join(self.appImage, '../..', libDir)),
os.path.abspath(os.path.join(self.appImage, '../../lib'))]
+ libPaths)
process = subprocess.Popen([self.appImage, # nosec
'-v',
'--no-appstream',
'--comp', 'xz',
appDir,
packagePath],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=penv)
process.communicate()
mutex.acquire()
print('Created AppImage package:')
self.printPackageInfo(packagePath)
mutex.release()
def package(self):
mutex = threading.Lock()
threads = [threading.Thread(target=self.createPortable, args=(mutex,))]
packagingTools = ['tar.xz']
if self.qtIFW != '':
threads.append(threading.Thread(target=self.createAppInstaller, args=(mutex,)))
packagingTools += ['Qt Installer Framework']
if self.appImage != '':
threads.append(threading.Thread(target=self.createAppImage, args=(mutex,)))
packagingTools += ['AppImage']
if len(packagingTools) > 0:
print('Detected packaging tools: {}\n'.format(', '.join(packagingTools)))
for thread in threads:
thread.start()
for thread in threads:
thread.join()