464 lines
17 KiB
Python
464 lines
17 KiB
Python
#!/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 math
|
|
import os
|
|
import subprocess # nosec
|
|
import sys
|
|
import threading
|
|
import zipfile
|
|
|
|
import deploy_base
|
|
import tools.binary_pecoff
|
|
import tools.qt5
|
|
|
|
|
|
class Deploy(deploy_base.DeployBase, tools.qt5.DeployToolsQt):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.targetSystem = 'posix_windows'
|
|
self.installDir = os.path.join(self.buildDir, 'ports/deploy/temp_priv')
|
|
self.pkgsDir = os.path.join(self.buildDir, 'ports/deploy/packages_auto/windows')
|
|
self.detectQt(os.path.join(self.buildDir, 'StandAlone'))
|
|
self.programName = 'webcamoid'
|
|
self.rootInstallDir = os.path.join(self.installDir, self.programName)
|
|
self.binaryInstallDir = os.path.join(self.rootInstallDir, 'bin')
|
|
self.libInstallDir = self.qmakeQuery(var='QT_INSTALL_LIBS') \
|
|
.replace(self.qmakeQuery(var='QT_INSTALL_PREFIX'),
|
|
self.rootInstallDir)
|
|
self.libQtInstallDir = self.qmakeQuery(var='QT_INSTALL_ARCHDATA') \
|
|
.replace(self.qmakeQuery(var='QT_INSTALL_PREFIX'),
|
|
self.rootInstallDir)
|
|
self.qmlInstallDir = self.qmakeQuery(var='QT_INSTALL_QML') \
|
|
.replace(self.qmakeQuery(var='QT_INSTALL_PREFIX'),
|
|
self.rootInstallDir)
|
|
self.pluginsInstallDir = self.qmakeQuery(var='QT_INSTALL_PLUGINS') \
|
|
.replace(self.qmakeQuery(var='QT_INSTALL_PREFIX'),
|
|
self.rootInstallDir)
|
|
self.qtConf = os.path.join(self.binaryInstallDir, 'qt.conf')
|
|
self.qmlRootDirs = ['StandAlone/share/qml', 'libAvKys/Plugins']
|
|
self.mainBinary = os.path.join(self.binaryInstallDir, self.programName + '.exe')
|
|
self.programName = os.path.splitext(os.path.basename(self.mainBinary))[0]
|
|
self.programVersion = self.detectVersion(os.path.join(self.rootDir, 'commons.pri'))
|
|
self.detectMake()
|
|
self.binarySolver = tools.binary_pecoff.DeployToolsBinary()
|
|
self.binarySolver.readExcludeList(os.path.join(self.rootDir, 'ports/deploy/exclude.{}.{}.txt'.format(os.name, sys.platform)))
|
|
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 = 256
|
|
self.appIcon = os.path.join(self.rootDir,
|
|
'StandAlone/share/themes/WebcamoidTheme/icons/hicolor/{1}x{1}/{0}.ico'.format(self.programName,
|
|
self.installerIconSize))
|
|
self.licenseFile = os.path.join(self.rootDir, 'COPYING')
|
|
self.installerRunProgram = '@TargetDir@/bin/' + self.programName + '.exe'
|
|
self.installerScript = os.path.join(self.rootDir, 'ports/deploy/installscript.windows.qs')
|
|
self.changeLog = os.path.join(self.rootDir, 'ChangeLog')
|
|
self.targetArch = '64bit' if 'x86_64' in self.qtInstallBins else '32bit'
|
|
|
|
@staticmethod
|
|
def removeUnneededFiles(path):
|
|
afiles = set()
|
|
|
|
for root, _, files in os.walk(path):
|
|
for f in files:
|
|
if f.endswith('.a') \
|
|
or f.endswith('.static.prl') \
|
|
or f.endswith('.pdb') \
|
|
or f.endswith('.lib'):
|
|
afiles.add(os.path.join(root, f))
|
|
|
|
for afile in afiles:
|
|
os.remove(afile)
|
|
|
|
def prepare(self):
|
|
print('Executing make install')
|
|
self.makeInstall(self.buildDir, self.installDir)
|
|
|
|
if self.targetArch == '32bit':
|
|
self.binarySolver.sysBinsPath = ['/usr/i686-w64-mingw32/bin']
|
|
else:
|
|
self.binarySolver.sysBinsPath = ['/usr/x86_64-w64-mingw32/bin']
|
|
|
|
self.binarySolver.detectStrip()
|
|
|
|
if self.qtIFWVersion == '' or int(self.qtIFWVersion.split('.')[0]) < 3:
|
|
appsDir = '@ApplicationsDir@'
|
|
else:
|
|
if self.targetArch == '32bit':
|
|
appsDir = '@ApplicationsDirX86@'
|
|
else:
|
|
appsDir = '@ApplicationsDirX64@'
|
|
|
|
self.installerTargetDir = appsDir + '/' + self.programName
|
|
arch = 'win32' if self.targetArch == '32bit' else 'win64'
|
|
self.outPackage = os.path.join(self.pkgsDir,
|
|
'{}-{}-{}.exe'.format(self.programName,
|
|
self.programVersion,
|
|
arch))
|
|
|
|
print('Copying Qml modules\n')
|
|
self.solvedepsQml()
|
|
print('\nCopying required plugins\n')
|
|
self.solvedepsPlugins()
|
|
print('\nRemoving Qt debug libraries')
|
|
self.removeDebugs()
|
|
print('Copying required libs\n')
|
|
self.solvedepsLibs()
|
|
print('\nWritting qt.conf file')
|
|
self.writeQtConf()
|
|
print('Stripping symbols')
|
|
self.binarySolver.stripSymbols(self.installDir)
|
|
print('Writting launcher file')
|
|
self.createLauncher()
|
|
print('Removing unnecessary files')
|
|
self.removeUnneededFiles(self.installDir)
|
|
print('\nWritting build system information\n')
|
|
self.writeBuildInfo()
|
|
|
|
def solvedepsLibs(self):
|
|
deps = set(self.binarySolver.scanDependencies(self.installDir))
|
|
extraDeps = ['libeay32.dll',
|
|
'ssleay32.dll',
|
|
'libEGL.dll',
|
|
'libGLESv2.dll',
|
|
'D3DCompiler_43.dll',
|
|
'D3DCompiler_46.dll',
|
|
'D3DCompiler_47.dll']
|
|
|
|
for dep in extraDeps:
|
|
path = self.whereBin(dep)
|
|
|
|
if path != '':
|
|
deps.add(path)
|
|
|
|
for depPath in self.binarySolver.allDependencies(path):
|
|
deps.add(depPath)
|
|
|
|
deps = sorted(deps)
|
|
|
|
for dep in deps:
|
|
depPath = os.path.join(self.binaryInstallDir, os.path.basename(dep))
|
|
|
|
if dep != depPath:
|
|
print(' {} -> {}'.format(dep, depPath))
|
|
self.copy(dep, depPath)
|
|
self.dependencies.append(dep)
|
|
|
|
def removeDebugs(self):
|
|
dbgFiles = set()
|
|
|
|
for root, _, files in os.walk(self.libQtInstallDir):
|
|
for f in files:
|
|
if f.endswith('.dll'):
|
|
fname, ext = os.path.splitext(f)
|
|
dbgFile = os.path.join(root, '{}d{}'.format(fname, ext))
|
|
|
|
if os.path.exists(dbgFile):
|
|
dbgFiles.add(dbgFile)
|
|
|
|
for f in dbgFiles:
|
|
os.remove(f)
|
|
|
|
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()
|
|
|
|
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()
|
|
|
|
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')
|
|
|
|
# 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 Wine version and emulated system info.
|
|
|
|
process = subprocess.Popen(['wine', '--version'], # nosec
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
stdout, _ = process.communicate()
|
|
wineVersion = stdout.decode(sys.getdefaultencoding()).strip()
|
|
|
|
with open(depsInfoFile, 'a') as f:
|
|
print(' Wine Version: {}'.format(wineVersion))
|
|
f.write('Wine Version: {}\n'.format(wineVersion))
|
|
|
|
process = subprocess.Popen(['wine', 'cmd', '/c', 'ver'], # nosec
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE)
|
|
stdout, _ = process.communicate()
|
|
fakeWindowsVersion = stdout.decode(sys.getdefaultencoding()).strip()
|
|
|
|
if len(fakeWindowsVersion) < 1:
|
|
fakeWindowsVersion = 'Unknown'
|
|
|
|
with open(depsInfoFile, 'a') as f:
|
|
print(' Windows Version: {}'.format(fakeWindowsVersion))
|
|
f.write('Windows Version: {}\n'.format(fakeWindowsVersion))
|
|
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) + '.bat'
|
|
libDir = os.path.relpath(self.libInstallDir, self.rootInstallDir)
|
|
|
|
with open(path, 'w') as launcher:
|
|
launcher.write('@echo off\n')
|
|
launcher.write('\n')
|
|
launcher.write('rem Default values: desktop | angle | software\n')
|
|
launcher.write('rem set QT_OPENGL=angle\n')
|
|
launcher.write('\n')
|
|
launcher.write('rem Default values: d3d11 | d3d9 | warp\n')
|
|
launcher.write('rem set QT_ANGLE_PLATFORM=d3d11\n')
|
|
launcher.write('\n')
|
|
launcher.write('rem Default values: software | d3d12 | openvg\n')
|
|
launcher.write('rem set QT_QUICK_BACKEND=""\n')
|
|
launcher.write('\n')
|
|
launcher.write('start /b "" '
|
|
+ '"%~dp0bin\\{}" '.format(self.programName)
|
|
+ '-p "%~dp0{}\\avkys" '.format(libDir)
|
|
+ '-c "%~dp0share\\config"\n')
|
|
|
|
@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):
|
|
arch = 'win32' if self.targetArch == '32bit' else 'win64'
|
|
packagePath = \
|
|
os.path.join(self.pkgsDir,
|
|
'{}-portable-{}-{}.zip'.format(self.programName,
|
|
self.programVersion,
|
|
arch))
|
|
|
|
if not os.path.exists(self.pkgsDir):
|
|
os.makedirs(self.pkgsDir)
|
|
|
|
with zipfile.ZipFile(packagePath, 'w', zipfile.ZIP_DEFLATED, False) as zipFile:
|
|
for root, dirs, files in os.walk(self.rootInstallDir):
|
|
for f in dirs + files:
|
|
filePath = os.path.join(root, f)
|
|
dstPath = os.path.join(self.programName,
|
|
filePath.replace(self.rootInstallDir + os.sep, ''))
|
|
zipFile.write(filePath, dstPath)
|
|
|
|
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 package(self):
|
|
mutex = threading.Lock()
|
|
|
|
threads = [threading.Thread(target=self.createPortable, args=(mutex,))]
|
|
packagingTools = ['zip']
|
|
|
|
if self.qtIFW != '':
|
|
threads.append(threading.Thread(target=self.createAppInstaller, args=(mutex,)))
|
|
packagingTools += ['Qt Installer Framework']
|
|
|
|
if len(packagingTools) > 0:
|
|
print('Detected packaging tools: {}\n'.format(', '.join(packagingTools)))
|
|
|
|
for thread in threads:
|
|
thread.start()
|
|
|
|
for thread in threads:
|
|
thread.join()
|