From 33a47f3c6042f4ed128fdf0682014d73bbd4e8fd Mon Sep 17 00:00:00 2001 From: Gonzalo Exequiel Pedone Date: Sun, 15 Nov 2020 13:04:02 -0300 Subject: [PATCH] Updated deploy scripts. --- README.md | 2 +- ports/deploy/deploy_android.py | 634 ------------------ ports/deploy/deploy_mac.py | 7 +- ports/deploy/deploy_posix.py | 2 +- ports/deploy/deploy_posix_windows.py | 7 +- ports/deploy/deploy_windows.py | 4 +- ports/deploy/package_info.conf | 1 - .../{ => tools/exclude}/exclude.nt.win32.txt | 0 .../exclude}/exclude.posix.darwin.txt | 0 .../exclude}/exclude.posix.freebsd12.txt | 0 .../exclude}/exclude.posix.linux.txt | 0 ports/deploy/tools/qt5.py | 38 +- ports/deploy/tools/utils.py | 12 +- share/icons/webcamoid.png | Bin 18934 -> 0 bytes 14 files changed, 43 insertions(+), 664 deletions(-) delete mode 100644 ports/deploy/deploy_android.py rename ports/deploy/{ => tools/exclude}/exclude.nt.win32.txt (100%) rename ports/deploy/{ => tools/exclude}/exclude.posix.darwin.txt (100%) rename ports/deploy/{ => tools/exclude}/exclude.posix.freebsd12.txt (100%) rename ports/deploy/{ => tools/exclude}/exclude.posix.linux.txt (100%) delete mode 100644 share/icons/webcamoid.png diff --git a/README.md b/README.md index e3f0d86..29b24be 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Visit the [wiki](https://github.com/webcamoid/akvirtualcamera/wiki) for a compre [![Build Status](https://travis-ci.org/webcamoid/akvirtualcamera.svg?branch=master)](https://travis-ci.org/webcamoid/akvirtualcamera) [![Build status](https://ci.appveyor.com/api/projects/status/rwd4of9casmfmmys?svg=true)](https://ci.appveyor.com/project/hipersayanX/akvirtualcamera) -[![Codacy Badge](https://app.codacy.com/project/badge/Grade/1cee2645a3604633a506a203fb8c3161)](https://www.codacy.com/gh/webcamoid/akvirtualcamera?utm_source=github.com&utm_medium=referral&utm_content=webcamoid/akvirtualcamera&utm_campaign=Badge_Grade) +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/1cee2645a3604633a506a203fb8c3161)](https://www.codacy.com/gh/webcamoid/akvirtualcamera/dashboard?utm_source=github.com&utm_medium=referral&utm_content=webcamoid/akvirtualcamera&utm_campaign=Badge_Grade) [![Daily Build](https://api.bintray.com/packages/webcamoid/webcamoid/akvirtualcamera/images/download.svg?version=daily)](https://bintray.com/webcamoid/webcamoid/akvirtualcamera/daily) ## Reporting Bugs ## diff --git a/ports/deploy/deploy_android.py b/ports/deploy/deploy_android.py deleted file mode 100644 index 9e8ed63..0000000 --- a/ports/deploy/deploy_android.py +++ /dev/null @@ -1,634 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# Webcamoid, webcam capture application. -# Copyright (C) 2019 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 . -# -# Web-Site: http://webcamoid.github.io/ - -import json -import math -import os -import re -import shutil -import subprocess # nosec -import sys -import threading -import zipfile - -import deploy_base -import tools.android -import tools.binary_elf -import tools.qt5 - - -class Deploy(deploy_base.DeployBase, - tools.qt5.DeployToolsQt, - tools.android.AndroidTools): - def __init__(self): - super().__init__() - self.targetSystem = 'android' - self.installDir = os.path.join(self.buildDir, 'ports/deploy/temp_priv') - self.pkgsDir = os.path.join(self.buildDir, 'ports/deploy/packages_auto/android') - self.standAloneDir = os.path.join(self.buildDir, 'StandAlone') - self.detectQt(self.standAloneDir) - self.programName = 'webcamoid' - self.binarySolver = tools.binary_elf.DeployToolsBinary() - self.detectAndroidPlatform(self.standAloneDir) - binary = self.detectTargetBinaryFromQt5Make(self.standAloneDir) - self.targetArch = self.binarySolver.machineEMCode(binary) - self.androidArchMap = {'AARCH64': 'arm64-v8a', - 'ARM' : 'armeabi-v7a', - '386' : 'x86', - 'X86_64' : 'x86_64'} - - if self.targetArch in self.androidArchMap: - self.targetArch = self.androidArchMap[self.targetArch] - - self.binarySolver.sysBinsPath = self.detectBinPaths() + self.binarySolver.sysBinsPath - self.binarySolver.libsSeachPaths = self.detectLibPaths() - self.rootInstallDir = os.path.join(self.installDir, self.programName) - self.libInstallDir = os.path.join(self.rootInstallDir, - 'libs', - self.targetArch) - self.binaryInstallDir = self.libInstallDir - self.assetsIntallDir = os.path.join(self.rootInstallDir, - 'assets', - 'android_rcc_bundle') - self.qmlInstallDir = os.path.join(self.assetsIntallDir, 'qml') - self.pluginsInstallDir = os.path.join(self.assetsIntallDir, 'plugins') - self.qtConf = os.path.join(self.binaryInstallDir, 'qt.conf') - self.qmlRootDirs = ['StandAlone/share/qml', 'libAvKys/Plugins'] - self.mainBinary = os.path.join(self.binaryInstallDir, os.path.basename(binary)) - self.programVersion = self.detectVersion(os.path.join(self.rootDir, 'commons.pri')) - self.detectMake() - self.binarySolver.readExcludeList(os.path.join(self.rootDir, 'ports/deploy/exclude.android.txt')) - self.binarySolver.libsSeachPaths += [self.qmakeQuery(var='QT_INSTALL_LIBS')] - self.packageConfig = os.path.join(self.rootDir, 'ports/deploy/package_info.conf') - self.dependencies = [] - - @staticmethod - def removeUnneededFiles(path): - afiles = set() - - for root, _, files in os.walk(path): - for f in files: - if f.endswith('.jar'): - 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.rootInstallDir) - self.binarySolver.detectStrip() - - if 'PACKAGES_MERGE' in os.environ \ - and len(os.environ['PACKAGES_MERGE']) > 0: - self.outPackage = \ - os.path.join(self.pkgsDir, - '{}-{}.apk'.format(self.programName, - self.programVersion)) - else: - self.outPackage = \ - os.path.join(self.pkgsDir, - '{}-{}-{}.apk'.format(self.programName, - self.programVersion, - self.targetArch)) - - print('Copying Qml modules\n') - self.solvedepsQml() - print('\nCopying required plugins\n') - self.solvedepsPlugins() - print('\nRemoving unused architectures') - self.removeInvalidArchs() - print('Fixing Android libs\n') - self.fixQtLibs() - - try: - shutil.rmtree(self.pluginsInstallDir) - except: - pass - - print('\nCopying required libs\n') - self.solvedepsLibs() - print('\nSolving Android dependencies\n') - self.solvedepsAndroid() - print('\nCopying Android build templates') - self.copyAndroidTemplates() - print('Fixing libs.xml file') - self.fixLibsXml() - print('Creating .rcc bundle file') - self.createRccBundle() - print('Stripping symbols') - self.binarySolver.stripSymbols(self.rootInstallDir) - print('Removing unnecessary files') - self.removeUnneededFiles(self.libInstallDir) - print('Writting build system information\n') - self.writeBuildInfo() - - def removeInvalidArchs(self): - suffix = '_{}.so'.format(self.targetArch) - - if not self.mainBinary.endswith(suffix): - return - - for root, dirs, files in os.walk(self.assetsIntallDir): - for f in files: - if f.endswith('.so') and not f.endswith(suffix): - os.remove(os.path.join(root, f)) - - def solvedepsLibs(self): - qtLibsPath = self.qmakeQuery(var='QT_INSTALL_LIBS') - self.binarySolver.ldLibraryPath.append(qtLibsPath) - self.qtLibs = sorted(self.binarySolver.scanDependencies(self.rootInstallDir)) - - for dep in self.qtLibs: - 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 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, 'assets') - - 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 SDK and NDK info. - - sdkInfoFile = os.path.join(self.androidSDK, 'tools', 'source.properties') - ndkInfoFile = os.path.join(self.androidNDK, 'source.properties') - - with open(depsInfoFile, 'a') as f: - platform = self.androidPlatform - print(' Android Platform: {}'.format(platform)) - f.write('Android Platform: {}\n'.format(platform)) - print(' SDK Info: \n') - f.write('SDK Info: \n\n') - - with open(sdkInfoFile) as sdkf: - for line in sdkf: - if len(line) > 0: - print(' ' + line.strip()) - f.write(' ' + line) - - print('\n NDK Info: \n') - f.write('\nNDK Info: \n\n') - - with open(ndkInfoFile) as ndkf: - for line in ndkf: - if len(line) > 0: - print(' ' + line.strip()) - f.write(' ' + line) - - 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') - - @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 alignPackage(self, package): - deploymentSettingsPath = '' - - for f in os.listdir(self.standAloneDir): - if re.match('^android-.+-deployment-settings.json$' , f): - deploymentSettingsPath = os.path.join(self.standAloneDir, f) - - break - - if len(deploymentSettingsPath) < 1: - return - - with open(deploymentSettingsPath) as f: - deploymentSettings = json.load(f) - - zipalign = os.path.join(self.androidSDK, - 'build-tools', - deploymentSettings['sdkBuildToolsRevision'], - 'zipalign') - - if self.system == 'windows': - zipalign += '.exe' - - alignedPackage = os.path.join(os.path.dirname(package), - 'aligned-' + os.path.basename(package)) - process = subprocess.Popen([zipalign, # nosec - '-v', - '-f', '4', - package, - alignedPackage], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - process.communicate() - - if process.returncode != 0: - return False - - self.move(alignedPackage, package) - - return True - - def apkSignPackage(self, package, keystore): - if not self.alignPackage(package): - return False - - deploymentSettingsPath = '' - - for f in os.listdir(self.standAloneDir): - if re.match('^android-.+-deployment-settings.json$' , f): - deploymentSettingsPath = os.path.join(self.standAloneDir, f) - - break - - if len(deploymentSettingsPath) < 1: - return - - with open(deploymentSettingsPath) as f: - deploymentSettings = json.load(f) - - apkSigner = os.path.join(self.androidSDK, - 'build-tools', - deploymentSettings['sdkBuildToolsRevision'], - 'apksigner') - - if self.system == 'windows': - apkSigner += '.exe' - - process = subprocess.Popen([apkSigner, # nosec - 'sign', - '-v', - '--ks', keystore, - '--ks-pass', 'pass:android', - '--ks-key-alias', 'androiddebugkey', - '--key-pass', 'pass:android', - package], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - process.communicate() - - return process.returncode == 0 - - def jarSignPackage(self, package, keystore): - jarSigner = 'jarsigner' - - if self.system == 'windows': - jarSigner += '.exe' - - jarSignerPath = '' - - if 'JAVA_HOME' in os.environ: - jarSignerPath = os.path.join(os.environ['JAVA_HOME'], - 'bin', - jarSigner) - - if len(jarSignerPath) < 1 or not os.path.exists(jarSignerPath): - jarSignerPath = self.whereBin(jarSigner) - - if len(jarSignerPath) < 1: - return False - - signedPackage = os.path.join(os.path.dirname(package), - 'signed-' + os.path.basename(package)) - process = subprocess.Popen([jarSignerPath, # nosec - '-verbose', - '-keystore', keystore, - '-storepass', 'android', - '-keypass', 'android', - '-sigalg', 'SHA1withRSA', - '-digestalg', 'SHA1', - '-sigfile', 'CERT', - '-signedjar', signedPackage, - package, - 'androiddebugkey'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - process.communicate() - - if process.returncode != 0: - return False - - self.move(signedPackage, package) - - return self.alignPackage(package) - - def signPackage(self, package): - keytool = 'keytool' - - if self.system == 'windows': - keytool += '.exe' - - keytoolPath = '' - - if 'JAVA_HOME' in os.environ: - keytoolPath = os.path.join(os.environ['JAVA_HOME'], 'bin', keytool) - - if len(keytoolPath) < 1 or not os.path.exists(keytoolPath): - keytoolPath = self.whereBin(keytool) - - if len(keytoolPath) < 1: - return False - - keystore = os.path.join(self.rootInstallDir, 'debug.keystore') - - if 'KEYSTORE_PATH' in os.environ: - keystore = os.environ['KEYSTORE_PATH'] - - if not os.path.exists(keystore): - try: - os.makedirs(os.path.dirname(keystore)) - except: - pass - - process = subprocess.Popen([keytoolPath, # nosec - '-genkey', - '-v', - '-storetype', 'pkcs12', - '-keystore', keystore, - '-storepass', 'android', - '-alias', 'androiddebugkey', - '-keypass', 'android', - '-keyalg', 'RSA', - '-keysize', '2048', - '-validity', '10000', - '-dname', 'CN=Android Debug,O=Android,C=US'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - process.communicate() - - if process.returncode != 0: - return False - - if self.apkSignPackage(package, keystore): - return True - - return self.jarSignPackage(package, keystore) - - def createApk(self, mutex): - if 'PACKAGES_MERGE' in os.environ: - print('Merging package data:\n') - - for path in os.environ['PACKAGES_MERGE'].split(':'): - path = path.strip() - - if os.path.exists(path) and os.path.isdir(path): - if path == self.buildDir: - continue - - standAlonePath = os.path.join(path, - os.path.relpath(self.standAloneDir, - self.buildDir)) - binary = self.detectTargetBinaryFromQt5Make(standAlonePath) - targetArch = self.binarySolver.machineEMCode(binary) - - if targetArch in self.androidArchMap: - targetArch = self.androidArchMap[targetArch] - - libsPath = os.path.join(path, - os.path.relpath(self.rootInstallDir, - self.buildDir), - 'libs', - targetArch) - dstLibPath = os.path.join(self.rootInstallDir, - 'libs', - targetArch) - print(' {} -> {}'.format(libsPath, dstLibPath)) - self.copy(libsPath, dstLibPath) - - print() - - if not os.path.exists(self.pkgsDir): - os.makedirs(self.pkgsDir) - - gradleSript = os.path.join(self.rootInstallDir, 'gradlew') - - if self.system == 'windows': - gradleSript += '.bat' - - os.chmod(gradleSript, 0o744) - process = subprocess.Popen([gradleSript, # nosec - '--no-daemon', - '--info', - 'assembleRelease'], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - cwd=self.rootInstallDir) - process.communicate() - apk = os.path.join(self.rootInstallDir, - 'build', - 'outputs', - 'apk', - 'release', - '{}-release-unsigned.apk'.format(self.programName)) - self.signPackage(apk) - self.copy(apk, self.outPackage) - - print('Created APK package:') - self.printPackageInfo(self.outPackage) - - def package(self): - mutex = threading.Lock() - - threads = [threading.Thread(target=self.createApk, args=(mutex,))] - packagingTools = ['apk'] - - if len(packagingTools) > 0: - print('Detected packaging tools: {}\n'.format(', '.join(packagingTools))) - - for thread in threads: - thread.start() - - for thread in threads: - thread.join() diff --git a/ports/deploy/deploy_mac.py b/ports/deploy/deploy_mac.py index a154a49..affa234 100644 --- a/ports/deploy/deploy_mac.py +++ b/ports/deploy/deploy_mac.py @@ -40,6 +40,7 @@ class Deploy(deploy_base.DeployBase, tools.qt5.DeployToolsQt): self.pkgsDir = os.path.join(self.buildDir, 'ports/deploy/packages_auto', self.targetSystem) self.detectQt(os.path.join(self.buildDir, 'Manager')) self.programName = 'AkVirtualCamera' + self.adminRights = True self.rootInstallDir = os.path.join(self.installDir, 'Applications') self.appBundleDir = os.path.join(self.rootInstallDir, self.programName + '.plugin') self.execPrefixDir = os.path.join(self.appBundleDir, 'Contents') @@ -48,12 +49,11 @@ class Deploy(deploy_base.DeployBase, tools.qt5.DeployToolsQt): self.programVersion = self.detectVersion(os.path.join(self.rootDir, 'commons.pri')) self.detectMake() self.binarySolver = tools.binary_mach.DeployToolsBinary() - self.binarySolver.readExcludeList(os.path.join(self.rootDir, 'ports/deploy/exclude.{}.{}.txt'.format(os.name, sys.platform))) + self.binarySolver.readExcludeList(os.path.join(self.rootDir, 'ports/deploy/tools/exclude/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.appIcon = os.path.join(self.rootDir, 'share/icons/webcamoid.png') self.licenseFile = os.path.join(self.rootDir, 'COPYING') self.installerTargetDir = '@ApplicationsDir@/' + self.programName self.installerScript = os.path.join(self.rootDir, 'ports/deploy/installscript.mac.qs') @@ -64,7 +64,8 @@ class Deploy(deploy_base.DeployBase, tools.qt5.DeployToolsQt): def prepare(self): print('Executing make install') - self.makeInstall(self.buildDir, self.installDir) + params = {'INSTALL_ROOT': self.installDir} + self.makeInstall(self.buildDir, params) self.detectTargetArch() print('Stripping symbols') self.binarySolver.stripSymbols(self.installDir) diff --git a/ports/deploy/deploy_posix.py b/ports/deploy/deploy_posix.py index 15e9b8e..ee3c570 100644 --- a/ports/deploy/deploy_posix.py +++ b/ports/deploy/deploy_posix.py @@ -53,7 +53,7 @@ class Deploy(deploy_base.DeployBase, tools.qt5.DeployToolsQt): self.targetSystem = 'android' self.binarySolver = tools.binary_elf.DeployToolsBinary() - self.binarySolver.readExcludeList(os.path.join(self.rootDir, 'ports/deploy/exclude.{}.{}.txt'.format(os.name, sys.platform))) + self.binarySolver.readExcludeList(os.path.join(self.rootDir, 'ports/deploy/tools/exclude/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') diff --git a/ports/deploy/deploy_posix_windows.py b/ports/deploy/deploy_posix_windows.py index f120d3c..191f331 100644 --- a/ports/deploy/deploy_posix_windows.py +++ b/ports/deploy/deploy_posix_windows.py @@ -39,6 +39,7 @@ class Deploy(deploy_base.DeployBase, tools.qt5.DeployToolsQt): self.pkgsDir = os.path.join(self.buildDir, 'ports/deploy/packages_auto/windows') self.detectQt(os.path.join(self.buildDir, 'Manager')) self.programName = 'AkVirtualCamera' + self.adminRights = True self.rootInstallDir = os.path.join(self.installDir, self.programName + '.plugin') self.binaryInstallDir = os.path.join(self.rootInstallDir, 'bin') self.mainBinary = os.path.join(self.binaryInstallDir, self.programName + '.exe') @@ -46,12 +47,11 @@ class Deploy(deploy_base.DeployBase, tools.qt5.DeployToolsQt): 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.binarySolver.readExcludeList(os.path.join(self.rootDir, 'ports/deploy/tools/exclude/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.appIcon = os.path.join(self.rootDir, 'share/icons/webcamoid.png') self.licenseFile = os.path.join(self.rootDir, 'COPYING') self.installerScript = os.path.join(self.rootDir, 'ports/deploy/installscript.windows.qs') self.changeLog = os.path.join(self.rootDir, 'ChangeLog') @@ -74,7 +74,8 @@ class Deploy(deploy_base.DeployBase, tools.qt5.DeployToolsQt): def prepare(self): print('Executing make install') - self.makeInstall(self.buildDir, self.installDir) + params = {'INSTALL_ROOT': self.installDir} + self.makeInstall(self.buildDir, params) if self.targetArch == '32bit': self.binarySolver.sysBinsPath = ['/usr/i686-w64-mingw32/bin'] diff --git a/ports/deploy/deploy_windows.py b/ports/deploy/deploy_windows.py index 1e7f571..38152fc 100644 --- a/ports/deploy/deploy_windows.py +++ b/ports/deploy/deploy_windows.py @@ -38,6 +38,7 @@ class Deploy(deploy_base.DeployBase, tools.qt5.DeployToolsQt): 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, 'Manager')) + self.adminRights = True self.programName = 'AkVirtualCamera' self.rootInstallDir = os.path.join(self.installDir, self.programName + '.plugin') self.binaryInstallDir = os.path.join(self.rootInstallDir, 'bin') @@ -46,12 +47,11 @@ class Deploy(deploy_base.DeployBase, tools.qt5.DeployToolsQt): 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.binarySolver.readExcludeList(os.path.join(self.rootDir, 'ports/deploy/tools/exclude/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.appIcon = os.path.join(self.rootDir, 'share/icons/webcamoid.png') self.licenseFile = os.path.join(self.rootDir, 'COPYING') self.installerScript = os.path.join(self.rootDir, 'ports/deploy/installscript.windows.qs') self.changeLog = os.path.join(self.rootDir, 'ChangeLog') diff --git a/ports/deploy/package_info.conf b/ports/deploy/package_info.conf index 639f115..a4b8530 100644 --- a/ports/deploy/package_info.conf +++ b/ports/deploy/package_info.conf @@ -2,5 +2,4 @@ appName = AkVirtualCamera description = AkVirtualCamera, virtual camera for Mac and Windows url = https://github.com/webcamoid/akvirtualcamera -titleColor = #3F1F7F licenseDescription = GNU General Public License v3.0 diff --git a/ports/deploy/exclude.nt.win32.txt b/ports/deploy/tools/exclude/exclude.nt.win32.txt similarity index 100% rename from ports/deploy/exclude.nt.win32.txt rename to ports/deploy/tools/exclude/exclude.nt.win32.txt diff --git a/ports/deploy/exclude.posix.darwin.txt b/ports/deploy/tools/exclude/exclude.posix.darwin.txt similarity index 100% rename from ports/deploy/exclude.posix.darwin.txt rename to ports/deploy/tools/exclude/exclude.posix.darwin.txt diff --git a/ports/deploy/exclude.posix.freebsd12.txt b/ports/deploy/tools/exclude/exclude.posix.freebsd12.txt similarity index 100% rename from ports/deploy/exclude.posix.freebsd12.txt rename to ports/deploy/tools/exclude/exclude.posix.freebsd12.txt diff --git a/ports/deploy/exclude.posix.linux.txt b/ports/deploy/tools/exclude/exclude.posix.linux.txt similarity index 100% rename from ports/deploy/exclude.posix.linux.txt rename to ports/deploy/tools/exclude/exclude.posix.linux.txt diff --git a/ports/deploy/tools/qt5.py b/ports/deploy/tools/qt5.py index 28f382e..51d1a11 100644 --- a/ports/deploy/tools/qt5.py +++ b/ports/deploy/tools/qt5.py @@ -47,7 +47,9 @@ class DeployToolsQt(tools.utils.DeployToolsUtils): self.dependencies = [] self.binarySolver = None self.installerConfig = '' + self.appIcon = '' self.installerRunProgram = '' + self.adminRights = False def detectQt(self, path=''): self.detectQmake(path) @@ -446,9 +448,14 @@ class DeployToolsQt(tools.utils.DeployToolsUtils): shutil.move(manifestTemp, manifest) def writeQtConf(self): - paths = {'Plugins': os.path.relpath(self.pluginsInstallDir, self.binaryInstallDir), - 'Imports': os.path.relpath(self.qmlInstallDir, self.binaryInstallDir), - 'Qml2Imports': os.path.relpath(self.qmlInstallDir, self.binaryInstallDir)} + prefix = self.binaryInstallDir + + if self.targetSystem == 'mac': + prefix = os.path.abspath(os.path.join(self.binaryInstallDir, '..')) + + paths = {'Plugins': os.path.relpath(self.pluginsInstallDir, prefix).replace('\\', '/'), + 'Imports': os.path.relpath(self.qmlInstallDir, prefix).replace('\\', '/'), + 'Qml2Imports': os.path.relpath(self.qmlInstallDir, prefix).replace('\\', '/')} confPath = os.path.dirname(self.qtConf) if not os.path.exists(confPath): @@ -496,7 +503,7 @@ class DeployToolsQt(tools.utils.DeployToolsUtils): packageConf.read(self.packageConfig, 'utf-8') # Create layout - componentName = 'com.webcamoidprj.{0}'.format(self.programName) + componentName = 'com.{0}prj.{0}'.format(self.programName) packageDir = os.path.join(self.installerPackages, componentName) if not os.path.exists(self.installerConfig): @@ -511,8 +518,12 @@ class DeployToolsQt(tools.utils.DeployToolsUtils): if not os.path.exists(metaDir): os.makedirs(metaDir) - self.copy(self.appIcon, self.installerConfig) - iconName = os.path.splitext(os.path.basename(self.appIcon))[0] + iconName = '' + + if self.appIcon != '' and os.path.exists(self.appIcon): + self.copy(self.appIcon, self.installerConfig) + iconName = os.path.splitext(os.path.basename(self.appIcon))[0] + licenseOutFile = os.path.basename(self.licenseFile) if not '.' in licenseOutFile and \ @@ -539,16 +550,18 @@ class DeployToolsQt(tools.utils.DeployToolsUtils): config.write(' {}\n'.format(packageConf['Package']['description'].strip())) config.write(' {}\n'.format(appName)) config.write(' {}\n'.format(packageConf['Package']['url'].strip())) - config.write(' {}\n'.format(iconName)) - config.write(' {}\n'.format(iconName)) - config.write(' {}\n'.format(iconName)) + + if iconName != '': + config.write(' {}\n'.format(iconName)) + config.write(' {}\n'.format(iconName)) + config.write(' {}\n'.format(iconName)) if self.installerRunProgram != '': config.write(' {}\n'.format(self.installerRunProgram)) config.write(' {}\n'.format(packageConf['Package']['runMessage'].strip())) config.write(' {}\n'.format(appName)) - config.write(' AkVirtualCameraUninstall\n') + config.write(' {}Uninstall\n'.format(appName)) config.write(' true\n') config.write(' {}\n'.format(self.installerTargetDir)) config.write('\n') @@ -585,7 +598,10 @@ class DeployToolsQt(tools.utils.DeployToolsUtils): f.write(' true\n') f.write(' true\n') f.write(' false\n') - f.write(' true\n') + + if self.adminRights: + f.write(' true\n') + f.write('\n') # Remove old file diff --git a/ports/deploy/tools/utils.py b/ports/deploy/tools/utils.py index ba14130..6f0edeb 100644 --- a/ports/deploy/tools/utils.py +++ b/ports/deploy/tools/utils.py @@ -212,16 +212,12 @@ class DeployToolsUtils: break - def makeInstall(self, buildDir, installRoot=''): + def makeInstall(self, buildDir, params={}): previousDir = os.getcwd() os.chdir(buildDir) - - if installRoot == '': - process = subprocess.Popen([self.make, 'install'], # nosec - stdout=subprocess.PIPE) - else: - process = subprocess.Popen([self.make, 'INSTALL_ROOT=' + installRoot, 'install'], # nosec - stdout=subprocess.PIPE) + params_ = [key + '=' + params[key] for key in params] + process = subprocess.Popen([self.make, 'install'] + params_, # nosec + stdout=subprocess.PIPE) process.communicate() os.chdir(previousDir) diff --git a/share/icons/webcamoid.png b/share/icons/webcamoid.png deleted file mode 100644 index 930d545d71d38e83d2193bdcd4c30ec90db8046f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18934 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