Added CI and deploy scripts.
This commit is contained in:
parent
eb25244ca5
commit
c9811d355d
38 changed files with 6058 additions and 0 deletions
112
.travis.yml
Normal file
112
.travis.yml
Normal file
|
@ -0,0 +1,112 @@
|
|||
language: cpp
|
||||
|
||||
cache:
|
||||
ccache: true
|
||||
timeout: 1000
|
||||
directories:
|
||||
- keystores
|
||||
|
||||
env:
|
||||
global:
|
||||
- QTVER=5.14.1
|
||||
- QTIFWVER=3.2.2
|
||||
- ARCH_ROOT_DATE=2020.04.01
|
||||
- ARCH_ROOT_URL=https://mirror.rackspace.com/archlinux
|
||||
# BT_KEY
|
||||
- secure: "RiR23dM8ft06+5ezNAlfpJXNRQhFH2Cy3hTY9oWD6nyw0qsydgOA9qlogBP0o1/kikyqt2uxv/VRcqZnBV3nkwEg9AuiCRgOuKqMtFdxwNnF8wqxUUz4pun9FQGLNnKkIznJkV61vsuRs8GSO95X3KEwgsoGPD60WQDDuFj3wXcI1kIN7HEP1t3Kq8lbhsYhbMPPeZBUGYcEjuWMz/GcfdcEUQLD6F0bkfEQgLZ+/d5Nqxk3NTJGM/LUYLKuWOthqsK7qNp/BCTRnOCg44NV3XO0DXti4lNkbtbfbLZIEMfD5EeTnbznBd+AHH2OTnw2Gn5ViVz7Z2C9mZgcN+/XyEJi7K80ab9DR9s+ZLMFO2yyF6MVf7/qiOzL0RT7lGFHt2HPLBpTji3qmgPN4JXaXKfF3nIzOjaoUibhTzwh5NlMpkAFlydmYwhBS3argsKQgeck3YTWUUnQVFvkmhFHVZzFBGtqy+0dPr9WGyay0mKKYpnLHqGk74K23dGnBKzk+iJs9o9zVQaSdvS5XnSeRsQCAYlqEKy/sSdrZnLYAOaLv1f291CIm76+e5B2sIkZaGcox7d3rcYw/tqn7ozgsKcyT1BJq8/ZyYSXsIT826ZeDiZd4MFjSQTeas5c9V85btDq2W9LvA66KqzTq0dH5pfJ67+BJJ7ebxUf4HNw6ME="
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
dist: bionic
|
||||
compiler: gcc
|
||||
env:
|
||||
- ARCH_ROOT_MINGW=x86_64
|
||||
- DAILY_BUILD=1
|
||||
- os: linux
|
||||
dist: bionic
|
||||
compiler: gcc
|
||||
env:
|
||||
- ARCH_ROOT_MINGW=i686
|
||||
- DAILY_BUILD=1
|
||||
- os: linux
|
||||
dist: bionic
|
||||
compiler: gcc
|
||||
env:
|
||||
- ARCH_ROOT_MINGW=x86_64
|
||||
- RELEASE_BUILD=1
|
||||
- os: linux
|
||||
dist: bionic
|
||||
compiler: gcc
|
||||
env:
|
||||
- ARCH_ROOT_MINGW=i686
|
||||
- RELEASE_BUILD=1
|
||||
- os: osx
|
||||
osx_image: xcode10
|
||||
compiler: clang
|
||||
env:
|
||||
- DAILY_BUILD=1
|
||||
- os: osx
|
||||
osx_image: xcode10
|
||||
compiler: clang
|
||||
env:
|
||||
- RELEASE_BUILD=1
|
||||
- os: osx
|
||||
osx_image: xcode10.1
|
||||
compiler: clang
|
||||
- os: osx
|
||||
osx_image: xcode10.2
|
||||
compiler: clang
|
||||
- os: osx
|
||||
osx_image: xcode10.3
|
||||
compiler: clang
|
||||
- os: osx
|
||||
osx_image: xcode11
|
||||
compiler: clang
|
||||
- os: osx
|
||||
osx_image: xcode11.1
|
||||
compiler: clang
|
||||
- os: osx
|
||||
osx_image: xcode11.2
|
||||
compiler: clang
|
||||
- os: osx
|
||||
osx_image: xcode11.3
|
||||
compiler: clang
|
||||
|
||||
install:
|
||||
- chmod +x ports/ci/travis/install_deps.sh
|
||||
- ports/ci/travis/install_deps.sh
|
||||
|
||||
before_script: |
|
||||
if [ "${TRAVIS_OS_NAME}" = linux ]; then
|
||||
export CXX=${ARCH_ROOT_MINGW}-w64-mingw32-g++
|
||||
export COMPILESPEC=win32-g++
|
||||
elif [ "${TRAVIS_OS_NAME}" = osx ]; then
|
||||
brew link --force qt5
|
||||
if [ "${CXX}" = g++ ]; then
|
||||
export COMPILESPEC=macx-g++
|
||||
elif [ "${CXX}" = clang++ ]; then
|
||||
export COMPILESPEC=macx-clang
|
||||
fi
|
||||
fi
|
||||
|
||||
script:
|
||||
- chmod +x ports/ci/travis/build.sh
|
||||
- ports/ci/travis/build.sh
|
||||
|
||||
#after_success:
|
||||
# - chmod +x ports/ci/travis/deploy.sh
|
||||
# - ports/ci/travis/deploy.sh
|
||||
# - chmod +x ports/ci/travis/upload.sh
|
||||
# - ports/ci/travis/upload.sh
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
notifications:
|
||||
recipients:
|
||||
- hipersayan.x@gmail.com
|
||||
email:
|
||||
on_success: change
|
||||
on_failure: change
|
82
appveyor.yml
Normal file
82
appveyor.yml
Normal file
|
@ -0,0 +1,82 @@
|
|||
version: 9.0.0.{build}-{branch}
|
||||
os: MinGW
|
||||
|
||||
image: Visual Studio 2019
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
build:
|
||||
parallel: true
|
||||
verbosity: detailed
|
||||
|
||||
configuration:
|
||||
- release
|
||||
# - debug
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
environment:
|
||||
global:
|
||||
PYTHON_VERSION: Python38
|
||||
QMAKESPEC: win32-g++
|
||||
MAKETOOL: mingw32-make
|
||||
INSTALL_PREFIX: C:/projects/akvirtualcamera/ports/deploy/temp_priv/usr
|
||||
# api key from https://bintray.com/profile/edit
|
||||
# encrypted in https://ci.appveyor.com/tools/encrypt
|
||||
BT_KEY:
|
||||
secure: seneRk4ppI4bquIsdweI8pd8FT0RXUvU2LOUNGSBEA28IhFQijypil2CfC3WtJxa
|
||||
|
||||
matrix:
|
||||
- MSYS2_BUILD: 1
|
||||
- QTDIR: C:\Qt\5.14\mingw73_64
|
||||
TOOLSDIR: C:\Qt\Tools\mingw730_64
|
||||
- QTDIR: C:\Qt\5.14\mingw73_32
|
||||
TOOLSDIR: C:\Qt\Tools\mingw730_32
|
||||
PLATFORM: x86
|
||||
- QTDIR: C:\Qt\5.14\msvc2017
|
||||
TOOLSDIR: C:\Qt\Tools\QtCreator
|
||||
QMAKESPEC: win32-msvc
|
||||
VSPATH: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build
|
||||
MAKETOOL: jom
|
||||
PLATFORM: x86
|
||||
- QTDIR: C:\Qt\5.14\msvc2017_64
|
||||
TOOLSDIR: C:\Qt\Tools\QtCreator
|
||||
QMAKESPEC: win32-msvc
|
||||
VSPATH: C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build
|
||||
MAKETOOL: jom
|
||||
|
||||
install:
|
||||
- if "%MSYS2_BUILD%" == "" (
|
||||
ports\ci\appveyor\install_deps.bat
|
||||
) else (
|
||||
C:\msys64\usr\bin\bash -lc "cd /c/projects/akvirtualcamera && ./ports/ci/appveyor/install_deps.sh"
|
||||
)
|
||||
|
||||
build_script:
|
||||
- if "%MSYS2_BUILD%" == "" (
|
||||
ports\ci\appveyor\build.bat
|
||||
) else (
|
||||
C:\msys64\usr\bin\bash -lc "cd /c/projects/akvirtualcamera && ./ports/ci/appveyor/build.sh '%INSTALL_PREFIX%'"
|
||||
)
|
||||
|
||||
#after_build:
|
||||
# - if "%MSYS2_BUILD%" == "" (
|
||||
# ports\ci\appveyor\deploy.bat
|
||||
# ) else (
|
||||
# C:\msys64\usr\bin\bash -lc "cd /c/projects/akvirtualcamera && ./ports/ci/appveyor/deploy.sh"
|
||||
# )
|
||||
# - ports\ci\appveyor\push_artifacts.bat
|
||||
#
|
||||
#deploy_script:
|
||||
# - ports\ci\appveyor\upload.bat
|
||||
|
||||
notifications:
|
||||
- provider: Email
|
||||
to:
|
||||
- hipersayan.x@gmail.com
|
||||
on_build_success: false
|
||||
on_build_failure: false
|
||||
on_build_status_changed: true
|
68
ports/ci/appveyor/build.bat
Normal file
68
ports/ci/appveyor/build.bat
Normal file
|
@ -0,0 +1,68 @@
|
|||
REM Webcamoid, webcam capture application.
|
||||
REM Copyright (C) 2016 Gonzalo Exequiel Pedone
|
||||
REM
|
||||
REM Webcamoid is free software: you can redistribute it and/or modify
|
||||
REM it under the terms of the GNU General Public License as published by
|
||||
REM the Free Software Foundation, either version 3 of the License, or
|
||||
REM (at your option) any later version.
|
||||
REM
|
||||
REM Webcamoid is distributed in the hope that it will be useful,
|
||||
REM but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
REM GNU General Public License for more details.
|
||||
REM
|
||||
REM You should have received a copy of the GNU General Public License
|
||||
REM along with Webcamoid. If not, see <http://www.gnu.org/licenses/>.
|
||||
REM
|
||||
REM Web-Site: http://webcamoid.github.io/
|
||||
|
||||
if "%PLATFORM%" == "x86" (
|
||||
set VC_ARGS=x86
|
||||
) else (
|
||||
set VC_ARGS=amd64
|
||||
)
|
||||
|
||||
rem Visual Studio init
|
||||
if not "%VSPATH%" == "" call "%VSPATH%\vcvarsall" %VC_ARGS%
|
||||
|
||||
set PATH_ORIG=%PATH%
|
||||
set PATH=%QTDIR%\bin;%TOOLSDIR%\bin;%PATH%
|
||||
|
||||
qmake -query
|
||||
qmake akvirtualcamera.pro ^
|
||||
CONFIG+=%CONFIGURATION% ^
|
||||
CONFIG+=silent ^
|
||||
PREFIX="%INSTALL_PREFIX%"
|
||||
|
||||
%MAKETOOL% -j4
|
||||
|
||||
if "%DAILY_BUILD%" == "" goto EndScript
|
||||
|
||||
if "%PLATFORM%" == "x86" (
|
||||
set DRV_ARCH=x64
|
||||
) else (
|
||||
set DRV_ARCH=x86
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Building %DRV_ARCH% virtual camera driver
|
||||
echo.
|
||||
|
||||
mkdir akvcam
|
||||
cd akvcam
|
||||
|
||||
set PATH=%QTDIR_ALT%\bin;%TOOLSDIR_ALT%\bin;%PATH_ORIG%
|
||||
qmake -query
|
||||
qmake ^
|
||||
CONFIG+=silent ^
|
||||
..\akvirtualcamera.pro
|
||||
%MAKETOOL% -j4
|
||||
|
||||
cd ..
|
||||
mkdir AkVirtualCamera.plugin\%DRV_ARCH%
|
||||
xcopy ^
|
||||
akvcam\AkVirtualCamera.plugin\%DRV_ARCH%\* ^
|
||||
AkVirtualCamera.plugin\%DRV_ARCH% ^
|
||||
/i /y
|
||||
|
||||
:EndScript
|
26
ports/ci/appveyor/build.sh
Executable file
26
ports/ci/appveyor/build.sh
Executable file
|
@ -0,0 +1,26 @@
|
|||
#!/bin/bash
|
||||
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Web-Site: http://webcamoid.github.io/
|
||||
|
||||
export PATH=/mingw64/bin:$PATH
|
||||
qmake -query
|
||||
qmake akvirtualcamera.pro \
|
||||
CONFIG+=silent \
|
||||
PREFIX="$1"
|
||||
make -j4
|
35
ports/ci/appveyor/deploy.bat
Normal file
35
ports/ci/appveyor/deploy.bat
Normal file
|
@ -0,0 +1,35 @@
|
|||
REM Webcamoid, webcam capture application.
|
||||
REM Copyright (C) 2017 Gonzalo Exequiel Pedone
|
||||
REM
|
||||
REM Webcamoid is free software: you can redistribute it and/or modify
|
||||
REM it under the terms of the GNU General Public License as published by
|
||||
REM the Free Software Foundation, either version 3 of the License, or
|
||||
REM (at your option) any later version.
|
||||
REM
|
||||
REM Webcamoid is distributed in the hope that it will be useful,
|
||||
REM but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
REM GNU General Public License for more details.
|
||||
REM
|
||||
REM You should have received a copy of the GNU General Public License
|
||||
REM along with Webcamoid. If not, see <http://www.gnu.org/licenses/>.
|
||||
REM
|
||||
REM Web-Site: http://webcamoid.github.io/
|
||||
|
||||
if "%PLATFORM%" == "x86" (
|
||||
set FF_ARCH=win32
|
||||
set GST_ARCH=x86
|
||||
set VC_ARGS=x86
|
||||
set PYTHON_PATH=C:\%PYTHON_VERSION%
|
||||
) else (
|
||||
set FF_ARCH=win64
|
||||
set GST_ARCH=x86_64
|
||||
set VC_ARGS=amd64
|
||||
set PYTHON_PATH=C:\%PYTHON_VERSION%-x64
|
||||
)
|
||||
|
||||
set MAKE_PATH=%TOOLSDIR%\bin\%MAKETOOL%.exe
|
||||
set GSTREAMER_DEV_PATH=C:\gstreamer\1.0\%GST_ARCH%
|
||||
set PATH=%QTDIR%\bin;%TOOLSDIR%\bin;%CD%\ffmpeg-%FFMPEG_VERSION%-%FF_ARCH%-shared\bin;%GSTREAMER_DEV_PATH%\bin;%PATH%
|
||||
|
||||
%PYTHON_PATH%\python.exe ports\deploy\deploy.py
|
22
ports/ci/appveyor/deploy.sh
Executable file
22
ports/ci/appveyor/deploy.sh
Executable file
|
@ -0,0 +1,22 @@
|
|||
#!/bin/bash
|
||||
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Web-Site: http://webcamoid.github.io/
|
||||
|
||||
export PATH=/mingw64/bin:$PATH
|
||||
python3 ports/deploy/deploy.py
|
20
ports/ci/appveyor/install_deps.bat
Normal file
20
ports/ci/appveyor/install_deps.bat
Normal file
|
@ -0,0 +1,20 @@
|
|||
REM Webcamoid, webcam capture application.
|
||||
REM Copyright (C) 2017 Gonzalo Exequiel Pedone
|
||||
REM
|
||||
REM Webcamoid is free software: you can redistribute it and/or modify
|
||||
REM it under the terms of the GNU General Public License as published by
|
||||
REM the Free Software Foundation, either version 3 of the License, or
|
||||
REM (at your option) any later version.
|
||||
REM
|
||||
REM Webcamoid is distributed in the hope that it will be useful,
|
||||
REM but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
REM GNU General Public License for more details.
|
||||
REM
|
||||
REM You should have received a copy of the GNU General Public License
|
||||
REM along with Webcamoid. If not, see <http://www.gnu.org/licenses/>.
|
||||
REM
|
||||
REM Web-Site: http://webcamoid.github.io/
|
||||
|
||||
rem Installing various utilities
|
||||
choco install -y jfrog-cli
|
28
ports/ci/appveyor/install_deps.sh
Executable file
28
ports/ci/appveyor/install_deps.sh
Executable file
|
@ -0,0 +1,28 @@
|
|||
#!/bin/bash
|
||||
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Web-Site: http://webcamoid.github.io/
|
||||
|
||||
pacman -Syy
|
||||
pacman --noconfirm --needed -S \
|
||||
git \
|
||||
make \
|
||||
pkg-config \
|
||||
python3 \
|
||||
mingw-w64-x86_64-pkg-config \
|
||||
mingw-w64-x86_64-qt5
|
23
ports/ci/appveyor/push_artifacts.bat
Normal file
23
ports/ci/appveyor/push_artifacts.bat
Normal file
|
@ -0,0 +1,23 @@
|
|||
REM Webcamoid, webcam capture application.
|
||||
REM Copyright (C) 2019 Gonzalo Exequiel Pedone
|
||||
REM
|
||||
REM Webcamoid is free software: you can redistribute it and/or modify
|
||||
REM it under the terms of the GNU General Public License as published by
|
||||
REM the Free Software Foundation, either version 3 of the License, or
|
||||
REM (at your option) any later version.
|
||||
REM
|
||||
REM Webcamoid is distributed in the hope that it will be useful,
|
||||
REM but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
REM GNU General Public License for more details.
|
||||
REM
|
||||
REM You should have received a copy of the GNU General Public License
|
||||
REM along with Webcamoid. If not, see <http://www.gnu.org/licenses/>.
|
||||
REM
|
||||
REM Web-Site: http://webcamoid.github.io/
|
||||
|
||||
if not "%DAILY_BUILD%" == "" (
|
||||
for %%f in (ports\deploy\packages_auto\windows\*.exe) do (
|
||||
appveyor PushArtifact %%f
|
||||
)
|
||||
)
|
35
ports/ci/appveyor/upload.bat
Normal file
35
ports/ci/appveyor/upload.bat
Normal file
|
@ -0,0 +1,35 @@
|
|||
REM Webcamoid, webcam capture application.
|
||||
REM Copyright (C) 2019 Gonzalo Exequiel Pedone
|
||||
REM
|
||||
REM Webcamoid is free software: you can redistribute it and/or modify
|
||||
REM it under the terms of the GNU General Public License as published by
|
||||
REM the Free Software Foundation, either version 3 of the License, or
|
||||
REM (at your option) any later version.
|
||||
REM
|
||||
REM Webcamoid is distributed in the hope that it will be useful,
|
||||
REM but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
REM MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
REM GNU General Public License for more details.
|
||||
REM
|
||||
REM You should have received a copy of the GNU General Public License
|
||||
REM along with Webcamoid. If not, see <http://www.gnu.org/licenses/>.
|
||||
REM
|
||||
REM Web-Site: http://webcamoid.github.io/
|
||||
|
||||
if not "%DAILY_BUILD%" == "" if "%APPVEYOR_REPO_BRANCH%" == "master" (
|
||||
jfrog bt config ^
|
||||
--user=hipersayanx ^
|
||||
--key=%BT_KEY% ^
|
||||
--licenses=GPL-3.0-or-later
|
||||
|
||||
for %%f in (ports\deploy\packages_auto\windows\*.exe) do (
|
||||
jfrog bt upload ^
|
||||
--user=hipersayanx ^
|
||||
--key=%BT_KEY% ^
|
||||
--override=true ^
|
||||
--publish=true ^
|
||||
%%f ^
|
||||
webcamoid/webcamoid/webcamoid/daily ^
|
||||
windows/
|
||||
)
|
||||
)
|
147
ports/ci/travis/build.sh
Executable file
147
ports/ci/travis/build.sh
Executable file
|
@ -0,0 +1,147 @@
|
|||
#!/bin/bash
|
||||
|
||||
# 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/
|
||||
|
||||
if [ -z "${DISABLE_CCACHE}" ]; then
|
||||
if [ "${CXX}" = clang++ ]; then
|
||||
UNUSEDARGS="-Qunused-arguments"
|
||||
fi
|
||||
|
||||
COMPILER="ccache ${CXX} ${UNUSEDARGS}"
|
||||
else
|
||||
COMPILER=${CXX}
|
||||
fi
|
||||
|
||||
if [ "${TRAVIS_OS_NAME}" = linux ]; then
|
||||
EXEC='sudo ./root.x86_64/bin/arch-chroot root.x86_64'
|
||||
fi
|
||||
|
||||
if [ "${TRAVIS_OS_NAME}" = linux ]; then
|
||||
sudo mount --bind root.x86_64 root.x86_64
|
||||
sudo mount --bind $HOME root.x86_64/$HOME
|
||||
|
||||
QMAKE_CMD=/usr/${ARCH_ROOT_MINGW}-w64-mingw32/lib/qt/bin/qmake
|
||||
|
||||
cat << EOF > ${BUILDSCRIPT}
|
||||
#!/bin/sh
|
||||
|
||||
export LC_ALL=C
|
||||
export HOME=$HOME
|
||||
EOF
|
||||
|
||||
if [ ! -z "${DAILY_BUILD}" ]; then
|
||||
cat << EOF >> ${BUILDSCRIPT}
|
||||
export DAILY_BUILD=1
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat << EOF >> ${BUILDSCRIPT}
|
||||
cd $TRAVIS_BUILD_DIR
|
||||
${QMAKE_CMD} -query
|
||||
${QMAKE_CMD} -spec ${COMPILESPEC} akvirtualcamera.pro \
|
||||
CONFIG+=silent \
|
||||
QMAKE_CXX="${COMPILER}"
|
||||
EOF
|
||||
chmod +x ${BUILDSCRIPT}
|
||||
sudo cp -vf ${BUILDSCRIPT} root.x86_64/$HOME/
|
||||
|
||||
${EXEC} bash $HOME/${BUILDSCRIPT}
|
||||
elif [ "${TRAVIS_OS_NAME}" = osx ]; then
|
||||
${EXEC} qmake -query
|
||||
${EXEC} qmake -spec ${COMPILESPEC} akvirtualcamera.pro \
|
||||
CONFIG+=silent \
|
||||
QMAKE_CXX="${COMPILER}"
|
||||
fi
|
||||
|
||||
if [ -z "${NJOBS}" ]; then
|
||||
NJOBS=4
|
||||
fi
|
||||
|
||||
if [ "${TRAVIS_OS_NAME}" = linux ]; then
|
||||
cat << EOF > ${BUILDSCRIPT}
|
||||
#!/bin/sh
|
||||
|
||||
export LC_ALL=C
|
||||
export HOME=$HOME
|
||||
EOF
|
||||
|
||||
if [ ! -z "${DAILY_BUILD}" ]; then
|
||||
cat << EOF >> ${BUILDSCRIPT}
|
||||
export DAILY_BUILD=1
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat << EOF >> ${BUILDSCRIPT}
|
||||
cd $TRAVIS_BUILD_DIR
|
||||
make -j${NJOBS}
|
||||
EOF
|
||||
chmod +x ${BUILDSCRIPT}
|
||||
sudo cp -vf ${BUILDSCRIPT} root.x86_64/$HOME/
|
||||
|
||||
${EXEC} bash $HOME/${BUILDSCRIPT}
|
||||
sudo umount root.x86_64/$HOME
|
||||
sudo umount root.x86_64
|
||||
else
|
||||
${EXEC} make -j${NJOBS}
|
||||
fi
|
||||
|
||||
if [ "${TRAVIS_OS_NAME}" = linux ]; then
|
||||
if [ "$ARCH_ROOT_MINGW" = x86_64 ]; then
|
||||
mingw_arch=i686
|
||||
mingw_compiler=${COMPILER/x86_64/i686}
|
||||
mingw_dstdir=x86
|
||||
else
|
||||
mingw_arch=x86_64
|
||||
mingw_compiler=${COMPILER/i686/x86_64}
|
||||
mingw_dstdir=x64
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Building $mingw_arch virtual camera driver"
|
||||
echo
|
||||
sudo mount --bind root.x86_64 root.x86_64
|
||||
sudo mount --bind $HOME root.x86_64/$HOME
|
||||
|
||||
cat << EOF > ${BUILDSCRIPT}
|
||||
#!/bin/sh
|
||||
|
||||
export LC_ALL=C
|
||||
export HOME=$HOME
|
||||
mkdir -p $TRAVIS_BUILD_DIR/akvcam
|
||||
cd $TRAVIS_BUILD_DIR/akvcam
|
||||
/usr/${mingw_arch}-w64-mingw32/lib/qt/bin/qmake \
|
||||
-spec ${COMPILESPEC} \
|
||||
../akvirtualcamera.pro \
|
||||
CONFIG+=silent \
|
||||
QMAKE_CXX="${mingw_compiler}"
|
||||
make -j${NJOBS}
|
||||
EOF
|
||||
chmod +x ${BUILDSCRIPT}
|
||||
sudo cp -vf ${BUILDSCRIPT} root.x86_64/$HOME/
|
||||
|
||||
${EXEC} bash $HOME/${BUILDSCRIPT}
|
||||
|
||||
sudo mkdir -p AkVirtualCamera.plugin/${mingw_dstdir}
|
||||
sudo cp -rvf \
|
||||
akvcam/AkVirtualCamera.plugin/${mingw_dstdir}/* \
|
||||
AkVirtualCamera.plugin/${mingw_dstdir}/
|
||||
|
||||
sudo umount root.x86_64/$HOME
|
||||
sudo umount root.x86_64
|
||||
fi
|
129
ports/ci/travis/deploy.sh
Executable file
129
ports/ci/travis/deploy.sh
Executable file
|
@ -0,0 +1,129 @@
|
|||
#!/bin/bash
|
||||
|
||||
# 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/
|
||||
|
||||
if [ "${ARCH_ROOT_BUILD}" = 1 ]; then
|
||||
EXEC='sudo ./root.x86_64/bin/arch-chroot root.x86_64'
|
||||
elif [ "${TRAVIS_OS_NAME}" = linux ] && [ -z "${ANDROID_BUILD}" ]; then
|
||||
if [ -z "${DAILY_BUILD}" ]; then
|
||||
EXEC="docker exec ${DOCKERSYS}"
|
||||
else
|
||||
EXEC="docker exec -e DAILY_BUILD=1 ${DOCKERSYS}"
|
||||
fi
|
||||
fi
|
||||
|
||||
DEPLOYSCRIPT=deployscript.sh
|
||||
|
||||
if [ "${ANDROID_BUILD}" = 1 ]; then
|
||||
export JAVA_HOME=$(readlink -f /usr/bin/java | sed 's:bin/java::')
|
||||
export ANDROID_HOME="${PWD}/build/android-sdk"
|
||||
export ANDROID_NDK="${PWD}/build/android-ndk"
|
||||
export ANDROID_NDK_HOME=${ANDROID_NDK}
|
||||
export ANDROID_NDK_PLATFORM=android-${ANDROID_PLATFORM}
|
||||
export ANDROID_NDK_ROOT=${ANDROID_NDK}
|
||||
export ANDROID_SDK_ROOT=${ANDROID_HOME}
|
||||
export PATH="${JAVA_HOME}/bin/java:${PATH}"
|
||||
export PATH="$PATH:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin"
|
||||
export PATH="${PATH}:${ANDROID_HOME}/platform-tools"
|
||||
export PATH="${PATH}:${ANDROID_HOME}/emulator"
|
||||
export PATH="${PATH}:${ANDROID_NDK}"
|
||||
export ORIG_PATH="${PATH}"
|
||||
export KEYSTORE_PATH="${PWD}/keystores/debug.keystore"
|
||||
nArchs=$(echo "${TARGET_ARCH}" | tr ':' ' ' | wc -w)
|
||||
lastArch=$(echo "${TARGET_ARCH}" | awk -F: '{print $NF}')
|
||||
|
||||
if [ "${nArchs}" = 1 ]; then
|
||||
export PATH="${PWD}/build/Qt/${QTVER}/android/bin:${PWD}/.local/bin:${ORIG_PATH}"
|
||||
export BUILD_PATH=${PWD}/build-webcamoid-${lastArch}
|
||||
|
||||
python3 ports/deploy/deploy.py
|
||||
else
|
||||
pkgMerge=
|
||||
|
||||
for arch_ in $(echo "${TARGET_ARCH}" | tr ":" "\n"); do
|
||||
if [ ! -z "${pkgMerge}" ]; then
|
||||
pkgMerge=${pkgMerge}:
|
||||
fi
|
||||
|
||||
pkgMerge=${pkgMerge}${PWD}/build-webcamoid-${arch_}
|
||||
done
|
||||
|
||||
for arch_ in $(echo "${TARGET_ARCH}" | tr ":" "\n"); do
|
||||
export PATH="${PWD}/build/Qt/${QTVER}/android/bin:${PWD}/.local/bin:${ORIG_PATH}"
|
||||
export BUILD_PATH=${PWD}/build-webcamoid-${arch_}
|
||||
|
||||
if [ "${arch_}" = "${lastArch}" ]; then
|
||||
export PACKAGES_PREPARE_ONLY=0
|
||||
export PACKAGES_MERGE="${pkgMerge}"
|
||||
else
|
||||
export PACKAGES_PREPARE_ONLY=1
|
||||
fi
|
||||
|
||||
export NO_SHOW_PKG_DATA_INFO=1
|
||||
|
||||
python3 ports/deploy/deploy.py
|
||||
done
|
||||
fi
|
||||
|
||||
mkdir -p "${PWD}/ports/deploy/packages_auto"
|
||||
cp -rvf "${PWD}/build-webcamoid-${lastArch}/ports/deploy/packages_auto"/* \
|
||||
"${PWD}/ports/deploy/packages_auto"
|
||||
elif [ "${ARCH_ROOT_BUILD}" = 1 ]; then
|
||||
sudo mount --bind root.x86_64 root.x86_64
|
||||
sudo mount --bind $HOME root.x86_64/$HOME
|
||||
|
||||
cat << EOF > ${DEPLOYSCRIPT}
|
||||
#!/bin/sh
|
||||
|
||||
export LC_ALL=C
|
||||
export HOME=$HOME
|
||||
export PATH="$TRAVIS_BUILD_DIR/.local/bin:\$PATH"
|
||||
export WINEPREFIX=/opt/.wine
|
||||
cd $TRAVIS_BUILD_DIR
|
||||
EOF
|
||||
|
||||
if [ ! -z "${DAILY_BUILD}" ]; then
|
||||
cat << EOF >> ${DEPLOYSCRIPT}
|
||||
export DAILY_BUILD=1
|
||||
EOF
|
||||
fi
|
||||
|
||||
cat << EOF >> ${DEPLOYSCRIPT}
|
||||
python ports/deploy/deploy.py
|
||||
EOF
|
||||
chmod +x ${DEPLOYSCRIPT}
|
||||
sudo cp -vf ${DEPLOYSCRIPT} root.x86_64/$HOME/
|
||||
|
||||
${EXEC} bash $HOME/${DEPLOYSCRIPT}
|
||||
sudo umount root.x86_64/$HOME
|
||||
sudo umount root.x86_64
|
||||
elif [ "${TRAVIS_OS_NAME}" = linux ]; then
|
||||
cat << EOF > ${DEPLOYSCRIPT}
|
||||
#!/bin/sh
|
||||
|
||||
export PATH="\$PWD/.local/bin:\$PATH"
|
||||
xvfb-run --auto-servernum python3 ports/deploy/deploy.py
|
||||
EOF
|
||||
|
||||
chmod +x ${DEPLOYSCRIPT}
|
||||
|
||||
${EXEC} bash ${DEPLOYSCRIPT}
|
||||
elif [ "${TRAVIS_OS_NAME}" = osx ]; then
|
||||
${EXEC} python3 ports/deploy/deploy.py
|
||||
fi
|
157
ports/ci/travis/install_deps.sh
Executable file
157
ports/ci/travis/install_deps.sh
Executable file
|
@ -0,0 +1,157 @@
|
|||
#!/bin/bash
|
||||
|
||||
# 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/
|
||||
|
||||
#qtIinstallerVerbose=-v
|
||||
|
||||
if [ ! -z "${USE_WGET}" ]; then
|
||||
export DOWNLOAD_CMD="wget -nv -c"
|
||||
else
|
||||
export DOWNLOAD_CMD="curl --retry 10 -sS -kLOC -"
|
||||
fi
|
||||
|
||||
if [ "${TRAVIS_OS_NAME}" = linux ] && [ "${ANDROID_BUILD}" != 1 ]; then
|
||||
if [ "${ARCH_ROOT_BUILD}" = 1 ]; then
|
||||
EXEC='sudo ./root.x86_64/bin/arch-chroot root.x86_64'
|
||||
else
|
||||
EXEC="docker exec ${DOCKERSYS}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${ARCH_ROOT_BUILD}" = 1 ]; then
|
||||
# Download chroot image
|
||||
archImage=archlinux-bootstrap-${ARCH_ROOT_DATE}-x86_64.tar.gz
|
||||
${DOWNLOAD_CMD} ${ARCH_ROOT_URL}/iso/${ARCH_ROOT_DATE}/$archImage
|
||||
sudo tar xzf $archImage
|
||||
|
||||
# Configure mirrors
|
||||
cp -vf root.x86_64/etc/pacman.conf .
|
||||
cat << EOF >> pacman.conf
|
||||
|
||||
[multilib]
|
||||
Include = /etc/pacman.d/mirrorlist
|
||||
|
||||
[ownstuff]
|
||||
Server = https://ftp.f3l.de/~martchus/\$repo/os/\$arch
|
||||
Server = http://martchus.no-ip.biz/repo/arch/\$repo/os/\$arch
|
||||
EOF
|
||||
sed -i 's/Required DatabaseOptional/Never/g' pacman.conf
|
||||
sed -i 's/#TotalDownload/TotalDownload/g' pacman.conf
|
||||
sudo cp -vf pacman.conf root.x86_64/etc/pacman.conf
|
||||
|
||||
cp -vf root.x86_64/etc/pacman.d/mirrorlist .
|
||||
cat << EOF >> mirrorlist
|
||||
|
||||
Server = ${ARCH_ROOT_URL}/\$repo/os/\$arch
|
||||
EOF
|
||||
sudo cp -vf mirrorlist root.x86_64/etc/pacman.d/mirrorlist
|
||||
|
||||
# Install packages
|
||||
sudo mkdir -pv root.x86_64/$HOME
|
||||
sudo mount --bind root.x86_64 root.x86_64
|
||||
sudo mount --bind $HOME root.x86_64/$HOME
|
||||
|
||||
${EXEC} pacman-key --init
|
||||
${EXEC} pacman-key --populate archlinux
|
||||
${EXEC} pacman -Syu \
|
||||
--noconfirm \
|
||||
--ignore linux,linux-api-headers,linux-docs,linux-firmware,linux-headers,pacman
|
||||
|
||||
${EXEC} pacman --noconfirm --needed -S \
|
||||
ccache \
|
||||
clang \
|
||||
file \
|
||||
git \
|
||||
make \
|
||||
pkgconf \
|
||||
python \
|
||||
sed \
|
||||
xorg-server-xvfb \
|
||||
wine \
|
||||
mingw-w64-pkg-config \
|
||||
mingw-w64-gcc \
|
||||
mingw-w64-qt5-base
|
||||
|
||||
for mingw_arch in i686 x86_64; do
|
||||
if [ "$mingw_arch" = x86_64 ]; then
|
||||
ff_arch=win64
|
||||
else
|
||||
ff_arch=win32
|
||||
fi
|
||||
|
||||
qtIFW=QtInstallerFramework-win-x86.exe
|
||||
|
||||
# Install Qt Installer Framework
|
||||
${DOWNLOAD_CMD} http://download.qt.io/official_releases/qt-installer-framework/${QTIFWVER}/${qtIFW} || true
|
||||
|
||||
if [ -e ${qtIFW} ]; then
|
||||
INSTALLSCRIPT=installscript.sh
|
||||
|
||||
cat << EOF > ${INSTALLSCRIPT}
|
||||
#!/bin/sh
|
||||
|
||||
export LC_ALL=C
|
||||
export HOME=$HOME
|
||||
export WINEPREFIX=/opt/.wine
|
||||
cd $TRAVIS_BUILD_DIR
|
||||
|
||||
wine ./${qtIFW} \
|
||||
${qtIinstallerVerbose} \
|
||||
--script "ports/ci/travis/qtifw_non_interactive_install.qs" \
|
||||
--no-force-installations
|
||||
EOF
|
||||
|
||||
chmod +x ${INSTALLSCRIPT}
|
||||
sudo cp -vf ${INSTALLSCRIPT} root.x86_64/$HOME/
|
||||
${EXEC} bash $HOME/${INSTALLSCRIPT}
|
||||
fi
|
||||
|
||||
# Finish
|
||||
sudo umount root.x86_64/$HOME
|
||||
sudo umount root.x86_64
|
||||
elif [ "${TRAVIS_OS_NAME}" = osx ]; then
|
||||
brew update
|
||||
brew upgrade
|
||||
brew link --overwrite numpy
|
||||
brew install \
|
||||
p7zip \
|
||||
python \
|
||||
ccache \
|
||||
pkg-config \
|
||||
qt5 \
|
||||
brew link --overwrite python
|
||||
|
||||
brew link python
|
||||
qtIFW=QtInstallerFramework-mac-x64.dmg
|
||||
|
||||
# Install Qt Installer Framework
|
||||
${DOWNLOAD_CMD} http://download.qt.io/official_releases/qt-installer-framework/${QTIFWVER}/${qtIFW} || true
|
||||
|
||||
if [ -e "${qtIFW}" ]; then
|
||||
hdiutil convert ${qtIFW} -format UDZO -o qtifw
|
||||
7z x -oqtifw qtifw.dmg -bb
|
||||
7z x -oqtifw qtifw/5.hfsx -bb
|
||||
chmod +x qtifw/QtInstallerFramework-mac-x64/QtInstallerFramework-mac-x64.app/Contents/MacOS/QtInstallerFramework-mac-x64
|
||||
|
||||
qtifw/QtInstallerFramework-mac-x64/QtInstallerFramework-mac-x64.app/Contents/MacOS/QtInstallerFramework-mac-x64 \
|
||||
${qtIinstallerVerbose} \
|
||||
--script "$PWD/ports/ci/travis/qtifw_non_interactive_install.qs" \
|
||||
--no-force-installations
|
||||
fi
|
||||
fi
|
62
ports/ci/travis/qt_non_interactive_install.qs
Normal file
62
ports/ci/travis/qt_non_interactive_install.qs
Normal file
|
@ -0,0 +1,62 @@
|
|||
function Controller()
|
||||
{
|
||||
installer.autoRejectMessageBoxes();
|
||||
installer.setMessageBoxAutomaticAnswer("OverwriteTargetDirectory", QMessageBox.Yes);
|
||||
installer.setMessageBoxAutomaticAnswer("stopProcessesForUpdates", QMessageBox.Ignore);
|
||||
installer.installationFinished.connect(function() {
|
||||
gui.clickButton(buttons.NextButton);
|
||||
})
|
||||
}
|
||||
|
||||
Controller.prototype.WelcomePageCallback = function()
|
||||
{
|
||||
gui.clickButton(buttons.NextButton, 10000);
|
||||
}
|
||||
|
||||
Controller.prototype.CredentialsPageCallback = function()
|
||||
{
|
||||
gui.clickButton(buttons.NextButton);
|
||||
}
|
||||
|
||||
Controller.prototype.IntroductionPageCallback = function()
|
||||
{
|
||||
gui.clickButton(buttons.NextButton);
|
||||
}
|
||||
|
||||
Controller.prototype.TargetDirectoryPageCallback = function()
|
||||
{
|
||||
//gui.currentPageWidget().TargetDirectoryLineEdit.setText(installer.value("HomeDir") + "/Qt");
|
||||
gui.currentPageWidget().TargetDirectoryLineEdit.setText(installer.value("InstallerDirPath") + "/Qt");
|
||||
gui.clickButton(buttons.NextButton);
|
||||
}
|
||||
|
||||
Controller.prototype.ComponentSelectionPageCallback = function()
|
||||
{
|
||||
var widget = gui.currentPageWidget();
|
||||
|
||||
widget.deselectAll();
|
||||
widget.selectComponent("qt.qt5.5141.android");
|
||||
|
||||
gui.clickButton(buttons.NextButton);
|
||||
}
|
||||
|
||||
Controller.prototype.LicenseAgreementPageCallback = function()
|
||||
{
|
||||
gui.currentPageWidget().AcceptLicenseRadioButton.setChecked(true);
|
||||
gui.clickButton(buttons.NextButton);
|
||||
}
|
||||
|
||||
Controller.prototype.ReadyForInstallationPageCallback = function()
|
||||
{
|
||||
gui.clickButton(buttons.NextButton);
|
||||
}
|
||||
|
||||
Controller.prototype.FinishedPageCallback = function()
|
||||
{
|
||||
var checkBoxForm = gui.currentPageWidget().LaunchQtCreatorCheckBoxForm
|
||||
|
||||
if (checkBoxForm && checkBoxForm.launchQtCreatorCheckBox)
|
||||
checkBoxForm.launchQtCreatorCheckBox.checked = false;
|
||||
|
||||
gui.clickButton(buttons.FinishButton);
|
||||
}
|
50
ports/ci/travis/qtifw_non_interactive_install.qs
Normal file
50
ports/ci/travis/qtifw_non_interactive_install.qs
Normal file
|
@ -0,0 +1,50 @@
|
|||
function Controller()
|
||||
{
|
||||
installer.autoRejectMessageBoxes();
|
||||
installer.setMessageBoxAutomaticAnswer("OverwriteTargetDirectory", QMessageBox.Yes);
|
||||
installer.setMessageBoxAutomaticAnswer("stopProcessesForUpdates", QMessageBox.Ignore);
|
||||
installer.installationFinished.connect(function() {
|
||||
gui.clickButton(buttons.NextButton);
|
||||
})
|
||||
}
|
||||
|
||||
Controller.prototype.IntroductionPageCallback = function()
|
||||
{
|
||||
gui.clickButton(buttons.NextButton);
|
||||
}
|
||||
|
||||
Controller.prototype.TargetDirectoryPageCallback = function()
|
||||
{
|
||||
gui.clickButton(buttons.NextButton);
|
||||
}
|
||||
|
||||
Controller.prototype.ComponentSelectionPageCallback = function()
|
||||
{
|
||||
gui.clickButton(buttons.NextButton);
|
||||
}
|
||||
|
||||
Controller.prototype.LicenseAgreementPageCallback = function()
|
||||
{
|
||||
gui.currentPageWidget().AcceptLicenseRadioButton.setChecked(true);
|
||||
gui.clickButton(buttons.NextButton);
|
||||
}
|
||||
|
||||
Controller.prototype.StartMenuDirectoryPageCallback = function()
|
||||
{
|
||||
gui.clickButton(buttons.NextButton);
|
||||
}
|
||||
|
||||
Controller.prototype.ReadyForInstallationPageCallback = function()
|
||||
{
|
||||
gui.clickButton(buttons.NextButton);
|
||||
}
|
||||
|
||||
Controller.prototype.PerformInstallationPageCallback = function()
|
||||
{
|
||||
gui.clickButton(buttons.NextButton);
|
||||
}
|
||||
|
||||
Controller.prototype.FinishedPageCallback = function()
|
||||
{
|
||||
gui.clickButton(buttons.FinishButton);
|
||||
}
|
100
ports/ci/travis/upload.sh
Executable file
100
ports/ci/travis/upload.sh
Executable file
|
@ -0,0 +1,100 @@
|
|||
#!/bin/bash
|
||||
|
||||
# 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/
|
||||
|
||||
if [ ! -z "${USE_WGET}" ]; then
|
||||
export DOWNLOAD_CMD="wget -nv -c"
|
||||
else
|
||||
export DOWNLOAD_CMD="curl --retry 10 -sS -kLOC -"
|
||||
fi
|
||||
|
||||
if [[ ( ! -z "$DAILY_BUILD" || ! -z "$RELEASE_BUILD" ) && "$TRAVIS_BRANCH" == "master" ]]; then
|
||||
if [ -z "$DAILY_BUILD" ]; then
|
||||
VER_MAJ=$(grep -re '^VER_MAJ[[:space:]]*=[[:space:]]*' commons.pri | awk '{print $3}')
|
||||
VER_MIN=$(grep -re '^VER_MIN[[:space:]]*=[[:space:]]*' commons.pri | awk '{print $3}')
|
||||
VER_PAT=$(grep -re '^VER_PAT[[:space:]]*=[[:space:]]*' commons.pri | awk '{print $3}')
|
||||
version=$VER_MAJ.$VER_MIN.$VER_PAT
|
||||
publish=false
|
||||
else
|
||||
version=daily
|
||||
publish=true
|
||||
fi
|
||||
|
||||
# Upload to Bintray
|
||||
|
||||
curl -fL https://getcli.jfrog.io | sh
|
||||
|
||||
./jfrog bt config \
|
||||
--user=hipersayanx \
|
||||
--key=$BT_KEY \
|
||||
--licenses=GPL-3.0-or-later
|
||||
|
||||
path=ports/deploy/packages_auto
|
||||
|
||||
for f in $(find $path -type f); do
|
||||
packagePath=${f#$path/}
|
||||
folder=$(dirname $packagePath)
|
||||
|
||||
./jfrog bt upload \
|
||||
--user=hipersayanx \
|
||||
--key=$BT_KEY \
|
||||
--override=true \
|
||||
--publish=$publish \
|
||||
$f \
|
||||
webcamoid/webcamoid/webcamoid/$version \
|
||||
$folder/
|
||||
done
|
||||
|
||||
# Upload to Github Releases
|
||||
upload=false
|
||||
|
||||
if [[ ! -z "$DAILY_BUILD" && "$TRAVIS_BRANCH" == master && "$upload" == true ]]; then
|
||||
hub=''
|
||||
|
||||
if [ "${TRAVIS_OS_NAME}" = linux ]; then
|
||||
hub=hub-linux-amd64-${GITHUB_HUBVER}
|
||||
else
|
||||
hub=hub-darwin-amd64-${GITHUB_HUBVER}
|
||||
fi
|
||||
|
||||
cd ${TRAVIS_BUILD_DIR}
|
||||
${DOWNLOAD_CMD} https://github.com/github/hub/releases/download/v${GITHUB_HUBVER}/${hub}.tgz || true
|
||||
tar xzf ${hub}.tgz
|
||||
mkdir -p .local
|
||||
cp -rf ${hub}/* .local/
|
||||
|
||||
export PATH="${PWD}/.local/bin:${PATH}"
|
||||
|
||||
hubTag=$(hub release -df '%T %t%n' | grep 'Daily Build' | awk '{print $1}' | sed 's/.*://')
|
||||
|
||||
if [ -z "$hubTag" ]; then
|
||||
hub release create -p -m 'Daily Build' daily
|
||||
hubTag=$(hub release -df '%T %t%n' | grep 'Daily Build' | awk '{print $1}' | sed 's/.*://')
|
||||
fi
|
||||
|
||||
if [ ! -z "$hubTag" ]; then
|
||||
path=ports/deploy/packages_auto
|
||||
|
||||
for f in $(find $path -type f); do
|
||||
hubTag=$(hub release -df '%T %t%n' | grep 'Daily Build' | awk '{print $1}' | sed 's/.*://')
|
||||
hub release edit -m 'Daily Build' -a "$f" "$hubTag"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
fi
|
41
ports/deploy/deploy.py
Executable file
41
ports/deploy/deploy.py
Executable file
|
@ -0,0 +1,41 @@
|
|||
#!/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 tools.utils
|
||||
|
||||
|
||||
if __name__ =='__main__':
|
||||
system = tools.utils.DeployToolsUtils().system
|
||||
|
||||
while True:
|
||||
try:
|
||||
deploy = __import__('deploy_' + system).Deploy()
|
||||
except:
|
||||
print('No valid deploy script found.')
|
||||
|
||||
exit()
|
||||
|
||||
if system == deploy.targetSystem:
|
||||
deploy.run()
|
||||
|
||||
exit()
|
||||
|
||||
system = deploy.targetSystem
|
634
ports/deploy/deploy_android.py
Normal file
634
ports/deploy/deploy_android.py
Normal file
|
@ -0,0 +1,634 @@
|
|||
#!/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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# 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()
|
112
ports/deploy/deploy_base.py
Normal file
112
ports/deploy/deploy_base.py
Normal file
|
@ -0,0 +1,112 @@
|
|||
#!/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 os
|
||||
import sys
|
||||
import platform
|
||||
import shutil
|
||||
|
||||
import tools.utils
|
||||
|
||||
class DeployBase(tools.utils.DeployToolsUtils):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.rootDir = os.path.normpath(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../..'))
|
||||
self.buildDir = os.environ['BUILD_PATH'] if 'BUILD_PATH' in os.environ else self.rootDir
|
||||
self.installDir = os.path.join(self.rootDir, 'ports/deploy/temp_priv/root')
|
||||
self.rootInstallDir = ''
|
||||
self.pkgsDir = os.path.join(self.rootDir,
|
||||
'ports/deploy/packages_auto',
|
||||
sys.platform if os.name == 'posix' else os.name)
|
||||
self.programVersion = ''
|
||||
self.qmake = ''
|
||||
|
||||
def __str__(self):
|
||||
deployInfo = 'Python version: {}\n' \
|
||||
'Root directory: {}\n' \
|
||||
'Build directory: {}\n' \
|
||||
'Install directory: {}\n' \
|
||||
'Packages directory: {}\n' \
|
||||
'System: {}\n' \
|
||||
'Architecture: {}\n' \
|
||||
'Target system: {}\n' \
|
||||
'Target architecture: {}\n' \
|
||||
'Number of threads: {}\n' \
|
||||
'Program version: {}\n' \
|
||||
'Make executable: {}\n' \
|
||||
'Qmake executable: {}'. \
|
||||
format(platform.python_version(),
|
||||
self.rootDir,
|
||||
self.buildDir,
|
||||
self.installDir,
|
||||
self.pkgsDir,
|
||||
self.system,
|
||||
self.arch,
|
||||
self.targetSystem,
|
||||
self.targetArch,
|
||||
self.njobs,
|
||||
self.programVersion,
|
||||
self.make,
|
||||
self.qmake)
|
||||
|
||||
return deployInfo
|
||||
|
||||
def run(self):
|
||||
print('Deploy info\n')
|
||||
print(self)
|
||||
print('\nPreparing for software packaging\n')
|
||||
self.prepare()
|
||||
|
||||
if not 'NO_SHOW_PKG_DATA_INFO' in os.environ \
|
||||
or os.environ['NO_SHOW_PKG_DATA_INFO'] != '1':
|
||||
print('\nPackaged data info\n')
|
||||
self.printPackageDataInfo()
|
||||
|
||||
if 'PACKAGES_PREPARE_ONLY' in os.environ \
|
||||
and os.environ['PACKAGES_PREPARE_ONLY'] == '1':
|
||||
print('\nPackage data is ready for merging\n')
|
||||
else:
|
||||
print('\nCreating packages\n')
|
||||
self.package()
|
||||
print('\nCleaning up')
|
||||
self.cleanup()
|
||||
print('Deploy finnished\n')
|
||||
|
||||
def printPackageDataInfo(self):
|
||||
packagedFiles = []
|
||||
|
||||
for root, _, files in os.walk(self.rootInstallDir):
|
||||
for f in files:
|
||||
packagedFiles.append(os.path.join(root, f))
|
||||
|
||||
packagedFiles = sorted(packagedFiles)
|
||||
|
||||
for f in packagedFiles:
|
||||
print(' ' + f)
|
||||
|
||||
def prepare(self):
|
||||
pass
|
||||
|
||||
def package(self):
|
||||
pass
|
||||
|
||||
def cleanup(self):
|
||||
shutil.rmtree(self.installDir, True)
|
499
ports/deploy/deploy_mac.py
Normal file
499
ports/deploy/deploy_mac.py
Normal file
|
@ -0,0 +1,499 @@
|
|||
#!/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 platform
|
||||
import shutil
|
||||
import subprocess # nosec
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
import deploy_base
|
||||
import tools.binary_mach
|
||||
import tools.qt5
|
||||
|
||||
|
||||
class Deploy(deploy_base.DeployBase, tools.qt5.DeployToolsQt):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.installDir = os.path.join(self.buildDir, 'ports/deploy/temp_priv')
|
||||
self.pkgsDir = os.path.join(self.buildDir, 'ports/deploy/packages_auto', self.targetSystem)
|
||||
self.detectQt(os.path.join(self.buildDir, 'StandAlone'))
|
||||
self.rootInstallDir = os.path.join(self.installDir, 'Applications')
|
||||
self.programName = 'webcamoid'
|
||||
self.appBundleDir = os.path.join(self.rootInstallDir, self.programName + '.app')
|
||||
self.execPrefixDir = os.path.join(self.appBundleDir, 'Contents')
|
||||
self.binaryInstallDir = os.path.join(self.execPrefixDir, 'MacOS')
|
||||
self.libInstallDir = os.path.join(self.execPrefixDir, 'Frameworks')
|
||||
self.qmlInstallDir = os.path.join(self.execPrefixDir, 'Resources/qml')
|
||||
self.pluginsInstallDir = os.path.join(self.execPrefixDir, 'Plugins')
|
||||
self.qtConf = os.path.join(self.execPrefixDir, 'Resources/qt.conf')
|
||||
self.qmlRootDirs = ['StandAlone/share/qml', 'libAvKys/Plugins']
|
||||
self.mainBinary = os.path.join(self.binaryInstallDir, self.programName)
|
||||
self.programVersion = self.detectVersion(os.path.join(self.rootDir, 'commons.pri'))
|
||||
self.detectMake()
|
||||
xspec = self.qmakeQuery(var='QMAKE_XSPEC')
|
||||
|
||||
if 'android' in xspec:
|
||||
self.targetSystem = 'android'
|
||||
|
||||
self.binarySolver = tools.binary_mach.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.appIcon = os.path.join(self.execPrefixDir, 'Resources/{0}.icns'.format(self.programName))
|
||||
self.licenseFile = os.path.join(self.rootDir, 'COPYING')
|
||||
self.installerRunProgram = '@TargetDir@/{0}.app/Contents/MacOS/{0}'.format(self.programName)
|
||||
self.installerTargetDir = '@ApplicationsDir@/' + self.programName
|
||||
self.installerScript = os.path.join(self.rootDir, 'ports/deploy/installscript.mac.qs')
|
||||
self.changeLog = os.path.join(self.rootDir, 'ChangeLog')
|
||||
self.outPackage = os.path.join(self.pkgsDir,
|
||||
'{}-{}.dmg'.format(self.programName,
|
||||
self.programVersion))
|
||||
|
||||
def prepare(self):
|
||||
print('Executing make install')
|
||||
self.makeInstall(self.buildDir, self.installDir)
|
||||
self.detectTargetArch()
|
||||
|
||||
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('Removing unnecessary files')
|
||||
self.removeUnneededFiles(self.libInstallDir)
|
||||
print('Fixing rpaths\n')
|
||||
self.fixRpaths()
|
||||
print('\nWritting build system information\n')
|
||||
self.writeBuildInfo()
|
||||
|
||||
def solvedepsLibs(self):
|
||||
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, not dep.endswith('.framework'))
|
||||
self.dependencies.append(dep)
|
||||
|
||||
@staticmethod
|
||||
def removeUnneededFiles(path):
|
||||
adirs = set()
|
||||
afiles = set()
|
||||
|
||||
for root, dirs, files in os.walk(path):
|
||||
for d in dirs:
|
||||
if d == 'Headers':
|
||||
adirs.add(os.path.join(root, d))
|
||||
|
||||
for f in files:
|
||||
if f == 'Headers' or f.endswith('.prl'):
|
||||
afiles.add(os.path.join(root, f))
|
||||
|
||||
for adir in adirs:
|
||||
try:
|
||||
shutil.rmtree(adir, True)
|
||||
except:
|
||||
pass
|
||||
|
||||
for afile in afiles:
|
||||
try:
|
||||
if os.path.islink(afile):
|
||||
os.unlink(afile)
|
||||
else:
|
||||
os.remove(afile)
|
||||
except:
|
||||
pass
|
||||
|
||||
def fixLibRpath(self, mutex, mach):
|
||||
rpath = os.path.join('@executable_path',
|
||||
os.path.relpath(self.libInstallDir,
|
||||
self.binaryInstallDir))
|
||||
log = '\tFixed {}\n\n'.format(mach)
|
||||
machInfo = self.binarySolver.dump(mach)
|
||||
|
||||
# Change rpath
|
||||
if mach.startswith(self.binaryInstallDir):
|
||||
log += '\t\tChanging rpath to {}\n'.format(rpath)
|
||||
|
||||
for oldRpath in machInfo['rpaths']:
|
||||
process = subprocess.Popen(['install_name_tool', # nosec
|
||||
'-delete_rpath', oldRpath, mach],
|
||||
stdout=subprocess.PIPE)
|
||||
process.communicate()
|
||||
|
||||
process = subprocess.Popen(['install_name_tool', # nosec
|
||||
'-add_rpath', rpath, mach],
|
||||
stdout=subprocess.PIPE)
|
||||
process.communicate()
|
||||
|
||||
# Change ID
|
||||
if mach.startswith(self.binaryInstallDir):
|
||||
newMachId = machInfo['id']
|
||||
elif mach.startswith(self.libInstallDir):
|
||||
newMachId = mach.replace(self.libInstallDir, rpath)
|
||||
else:
|
||||
newMachId = os.path.basename(mach)
|
||||
|
||||
if newMachId != machInfo['id']:
|
||||
log += '\t\tChanging ID to {}\n'.format(newMachId)
|
||||
|
||||
process = subprocess.Popen(['install_name_tool', # nosec
|
||||
'-id', newMachId, mach],
|
||||
stdout=subprocess.PIPE)
|
||||
process.communicate()
|
||||
|
||||
# Change library links
|
||||
for dep in machInfo['imports']:
|
||||
if dep.startswith(rpath):
|
||||
continue
|
||||
|
||||
if self.binarySolver.isExcluded(dep):
|
||||
continue
|
||||
|
||||
basename = os.path.basename(dep)
|
||||
framework = ''
|
||||
inFrameworkPath = ''
|
||||
|
||||
if not basename.endswith('.dylib'):
|
||||
frameworkPath = dep[: dep.rfind('.framework')] + '.framework'
|
||||
framework = os.path.basename(frameworkPath)
|
||||
inFrameworkPath = os.path.join(framework, dep.replace(frameworkPath + '/', ''))
|
||||
|
||||
newDepPath = os.path.join(rpath, basename if len(framework) < 1 else inFrameworkPath)
|
||||
|
||||
if dep != newDepPath:
|
||||
log += '\t\t{} -> {}\n'.format(dep, newDepPath)
|
||||
|
||||
process = subprocess.Popen(['install_name_tool', # nosec
|
||||
'-change', dep, newDepPath, mach],
|
||||
stdout=subprocess.PIPE)
|
||||
process.communicate()
|
||||
|
||||
mutex.acquire()
|
||||
print(log)
|
||||
mutex.release()
|
||||
|
||||
def fixRpaths(self):
|
||||
path = os.path.join(self.execPrefixDir)
|
||||
mutex = threading.Lock()
|
||||
threads = []
|
||||
|
||||
for mach in self.binarySolver.find(path):
|
||||
thread = threading.Thread(target=self.fixLibRpath, args=(mutex, mach,))
|
||||
threads.append(thread)
|
||||
|
||||
while threading.active_count() >= self.njobs:
|
||||
time.sleep(0.25)
|
||||
|
||||
thread.start()
|
||||
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
@staticmethod
|
||||
def searchPackageFor(cellarPath, path):
|
||||
if not path.startswith(cellarPath):
|
||||
return ''
|
||||
|
||||
return ' '.join(path.replace(cellarPath + os.sep, '').split(os.sep)[0: 2])
|
||||
|
||||
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():
|
||||
process = subprocess.Popen(['sw_vers'], # nosec
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, _ = process.communicate()
|
||||
|
||||
return stdout.decode(sys.getdefaultencoding()).strip()
|
||||
|
||||
def writeBuildInfo(self):
|
||||
resourcesDir = os.path.join(self.execPrefixDir, 'Resources')
|
||||
os.makedirs(self.pkgsDir)
|
||||
depsInfoFile = os.path.join(resourcesDir, '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')
|
||||
|
||||
os.environ['LC_ALL'] = 'C'
|
||||
brew = self.whereBin('brew')
|
||||
|
||||
if len(brew) < 1:
|
||||
return
|
||||
|
||||
process = subprocess.Popen([brew, '--cellar'], # nosec
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, _ = process.communicate()
|
||||
cellarPath = stdout.decode(sys.getdefaultencoding()).strip()
|
||||
|
||||
# Write binary dependencies info.
|
||||
|
||||
packages = set()
|
||||
|
||||
for dep in self.dependencies:
|
||||
packageInfo = self.searchPackageFor(cellarPath, 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')
|
||||
|
||||
@staticmethod
|
||||
def dirSize(path):
|
||||
size = 0
|
||||
|
||||
for root, _, files in os.walk(path):
|
||||
for f in files:
|
||||
fpath = os.path.join(root, f)
|
||||
|
||||
if not os.path.islink(fpath):
|
||||
size += os.path.getsize(fpath)
|
||||
|
||||
return size
|
||||
|
||||
# https://asmaloney.com/2013/07/howto/packaging-a-mac-os-x-application-using-a-dmg/
|
||||
def createPortable(self, mutex):
|
||||
staggingDir = os.path.join(self.installDir, 'stagging')
|
||||
|
||||
if not os.path.exists(staggingDir):
|
||||
os.makedirs(staggingDir)
|
||||
|
||||
self.copy(self.appBundleDir,
|
||||
os.path.join(staggingDir, self.programName + '.app'))
|
||||
imageSize = self.dirSize(staggingDir)
|
||||
tmpDmg = os.path.join(self.installDir, self.programName + '_tmp.dmg')
|
||||
volumeName = "{}-portable-{}".format(self.programName,
|
||||
self.programVersion)
|
||||
|
||||
process = subprocess.Popen(['hdiutil', 'create', # nosec
|
||||
'-srcfolder', staggingDir,
|
||||
'-volname', volumeName,
|
||||
'-fs', 'HFS+',
|
||||
'-fsargs', '-c c=64,a=16,e=16',
|
||||
'-format', 'UDRW',
|
||||
'-size', str(math.ceil(imageSize * 1.1)),
|
||||
tmpDmg],
|
||||
stdout=subprocess.PIPE)
|
||||
process.communicate()
|
||||
|
||||
process = subprocess.Popen(['hdiutil', # nosec
|
||||
'attach',
|
||||
'-readwrite',
|
||||
'-noverify',
|
||||
tmpDmg],
|
||||
stdout=subprocess.PIPE)
|
||||
stdout, _ = process.communicate()
|
||||
device = ''
|
||||
|
||||
for line in stdout.split(b'\n'):
|
||||
line = line.strip()
|
||||
|
||||
if len(line) < 1:
|
||||
continue
|
||||
|
||||
dev = line.split()
|
||||
|
||||
if len(dev) > 2:
|
||||
device = dev[0].decode(sys.getdefaultencoding())
|
||||
|
||||
break
|
||||
|
||||
time.sleep(2)
|
||||
volumePath = os.path.join('/Volumes', volumeName)
|
||||
volumeIcon = os.path.join(volumePath, '.VolumeIcon.icns')
|
||||
self.copy(self.appIcon, volumeIcon)
|
||||
|
||||
process = subprocess.Popen(['SetFile', # nosec
|
||||
'-c', 'icnC',
|
||||
volumeIcon],
|
||||
stdout=subprocess.PIPE)
|
||||
process.communicate()
|
||||
|
||||
process = subprocess.Popen(['SetFile', # nosec
|
||||
'-a', 'C',
|
||||
volumePath],
|
||||
stdout=subprocess.PIPE)
|
||||
process.communicate()
|
||||
|
||||
appsShortcut = os.path.join(volumePath, 'Applications')
|
||||
|
||||
if not os.path.exists(appsShortcut):
|
||||
os.symlink('/Applications', appsShortcut)
|
||||
|
||||
os.sync()
|
||||
|
||||
process = subprocess.Popen(['hdiutil', # nosec
|
||||
'detach',
|
||||
device],
|
||||
stdout=subprocess.PIPE)
|
||||
process.communicate()
|
||||
|
||||
packagePath = \
|
||||
os.path.join(self.pkgsDir,
|
||||
'{}-portable-{}-{}.dmg'.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)
|
||||
|
||||
process = subprocess.Popen(['hdiutil', # nosec
|
||||
'convert',
|
||||
tmpDmg,
|
||||
'-format', 'UDZO',
|
||||
'-imagekey', 'zlib-level=9',
|
||||
'-o', packagePath],
|
||||
stdout=subprocess.PIPE)
|
||||
process.communicate()
|
||||
|
||||
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 = ['dmg']
|
||||
|
||||
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()
|
497
ports/deploy/deploy_posix.py
Normal file
497
ports/deploy/deploy_posix.py
Normal file
|
@ -0,0 +1,497 @@
|
|||
#!/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
|
||||
|
||||
import deploy_base
|
||||
import tools.binary_elf
|
||||
import tools.qt5
|
||||
|
||||
|
||||
class Deploy(deploy_base.DeployBase, tools.qt5.DeployToolsQt):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.installDir = os.path.join(self.buildDir, 'ports/deploy/temp_priv')
|
||||
self.pkgsDir = os.path.join(self.buildDir, 'ports/deploy/packages_auto', sys.platform)
|
||||
self.detectQt(os.path.join(self.buildDir, 'StandAlone'))
|
||||
self.rootInstallDir = os.path.join(self.installDir, self.qmakeQuery(var='QT_INSTALL_PREFIX')[1:])
|
||||
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, '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'
|
||||
|
||||
self.binarySolver = tools.binary_elf.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 = 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,
|
||||
'webcamoid-installer-{}-{}.run'.format(self.programVersion,
|
||||
platform.machine()))
|
||||
|
||||
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()
|
464
ports/deploy/deploy_posix_windows.py
Normal file
464
ports/deploy/deploy_posix_windows.py
Normal file
|
@ -0,0 +1,464 @@
|
|||
#!/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()
|
393
ports/deploy/deploy_windows.py
Normal file
393
ports/deploy/deploy_windows.py
Normal file
|
@ -0,0 +1,393 @@
|
|||
#!/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 platform
|
||||
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.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.rootInstallDir = os.path.join(self.installDir, 'usr')
|
||||
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, 'webcamoid.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()
|
||||
xspec = self.qmakeQuery(var='QMAKE_XSPEC')
|
||||
|
||||
if 'android' in xspec:
|
||||
self.targetSystem = 'android'
|
||||
|
||||
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')
|
||||
|
||||
def prepare(self):
|
||||
print('Executing make install')
|
||||
self.makeInstall(self.buildDir)
|
||||
self.detectTargetArch()
|
||||
|
||||
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,
|
||||
'webcamoid-{}-{}.exe'.format(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:
|
||||
dep = dep.replace('\\', '/')
|
||||
depPath = os.path.join(self.binaryInstallDir, os.path.basename(dep))
|
||||
depPath = depPath.replace('\\', '/')
|
||||
|
||||
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 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 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 searchPackageFor(self, path):
|
||||
path = path.replace('C:/', '/c/')
|
||||
os.environ['LC_ALL'] = 'C'
|
||||
pacman = self.whereBin('pacman.exe')
|
||||
|
||||
if len(pacman) > 0:
|
||||
process = subprocess.Popen([pacman, '-Qo', path], # nosec
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, _ = process.communicate()
|
||||
|
||||
if process.returncode != 0:
|
||||
prefix = '/c/msys32' if self.targetArch == '32bit' else '/c/msys64'
|
||||
path = path[len(prefix):]
|
||||
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()])
|
||||
|
||||
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():
|
||||
try:
|
||||
process = subprocess.Popen(['cmd', '/c', 'ver'], # nosec
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, _ = process.communicate()
|
||||
windowsVersion = stdout.decode(sys.getdefaultencoding()).strip()
|
||||
|
||||
return 'Windows Version: {}\n'.format(windowsVersion)
|
||||
except:
|
||||
pass
|
||||
|
||||
return ' '.join(platform.uname())
|
||||
|
||||
def writeBuildInfo(self):
|
||||
shareDir = os.path.join(self.rootInstallDir, 'share')
|
||||
|
||||
try:
|
||||
os.makedirs(shareDir)
|
||||
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 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 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()
|
2
ports/deploy/exclude.nt.win32.txt
Normal file
2
ports/deploy/exclude.nt.win32.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
C:/Windows/System32/.*
|
||||
C:/Program Files/.*
|
2
ports/deploy/exclude.posix.darwin.txt
Normal file
2
ports/deploy/exclude.posix.darwin.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
/usr/lib/.*
|
||||
/System/Library/Frameworks/.*
|
110
ports/deploy/exclude.posix.freebsd12.txt
Normal file
110
ports/deploy/exclude.posix.freebsd12.txt
Normal file
|
@ -0,0 +1,110 @@
|
|||
# VDSO
|
||||
(.*/)*ld-linux.so.2
|
||||
(.*/)*ld-linux-x86-64.so.2
|
||||
|
||||
# Glibc
|
||||
(.*/)*libc.so.[0-9]+
|
||||
(.*/)*libdl.so.[0-9]+
|
||||
(.*/)*libm.so.[0-9]+
|
||||
(.*/)*libmvec.so.[0-9]+
|
||||
(.*/)*libpthread.so.[0-9]+
|
||||
(.*/)*libresolv.so.[0-9]+
|
||||
(.*/)*librt.so.[0-9]+
|
||||
|
||||
# GCC
|
||||
(.*/)*libgcc_s.so.[0-9]+
|
||||
(.*/)*libgomp.so.[0-9]+
|
||||
(.*/)*libstdc\+\+.so.[0-9]+
|
||||
|
||||
# Core libraries
|
||||
(.*/)*libSM.so.6
|
||||
(.*/)*libblkid.so.1
|
||||
(.*/)*libcap.so.2
|
||||
(.*/)*libcom_err.so.2
|
||||
(.*/)*libcrypto.so.1.0.0
|
||||
(.*/)*libdb-5.3.so
|
||||
(.*/)*libdbus-1.so.3
|
||||
(.*/)*libexpat.so.1
|
||||
(.*/)*libfontconfig.so.1
|
||||
(.*/)*libfreetype.so.6
|
||||
(.*/)*libgcrypt.so.20
|
||||
(.*/)*libgmp.so.10
|
||||
(.*/)*libgpg-error.so.0
|
||||
(.*/)*libgssapi_krb5.so.2
|
||||
(.*/)*libharfbuzz.so.0
|
||||
(.*/)*libk5crypto.so.3
|
||||
(.*/)*libkeyutils.so.1
|
||||
(.*/)*libkrb5.so.3
|
||||
(.*/)*libkrb5support.so.0
|
||||
(.*/)*liblz4.so.1
|
||||
(.*/)*liblzma.so.5
|
||||
(.*/)*libmount.so.1
|
||||
(.*/)*libpcre.so.[0-9]+$
|
||||
(.*/)*libpcre16.so.0
|
||||
(.*/)*libssh2.so.1
|
||||
(.*/)*libssl.so.1.0.0
|
||||
(.*/)*libtasn1.so.6
|
||||
(.*/)*libusb-1.0.so.0
|
||||
(.*/)*libuuid.so.1
|
||||
(.*/)*libz.so.1
|
||||
|
||||
# Glib2
|
||||
(.*/)*libgio-2.0.so.0
|
||||
(.*/)*libglib-2.0.so.0
|
||||
(.*/)*libgmodule-2.0.so.0
|
||||
(.*/)*libgobject-2.0.so.0
|
||||
|
||||
# X11
|
||||
(.*/)*libX11-xcb.so.[0-9]+
|
||||
(.*/)*libX11.so.[0-9]+
|
||||
(.*/)*libXau.so.[0-9]+
|
||||
(.*/)*libXcursor.so.[0-9]+
|
||||
(.*/)*libXdamage.so.[0-9]+
|
||||
(.*/)*libXdmcp.so.[0-9]+
|
||||
(.*/)*libXext.so.[0-9]+
|
||||
(.*/)*libXfixes.so.[0-9]+
|
||||
(.*/)*libXi.so.[0-9]+
|
||||
(.*/)*libXinerama.so.[0-9]+
|
||||
(.*/)*libXrandr.so.[0-9]+
|
||||
(.*/)*libXrender.so.[0-9]+
|
||||
(.*/)*libXss.so.[0-9]+
|
||||
(.*/)*libXv.so.[0-9]+
|
||||
(.*/)*libXxf86vm.so.[0-9]+
|
||||
(.*/)*libglapi.so.[0-9]+
|
||||
(.*/)*libp11-kit.so.[0-9]+
|
||||
(.*/)*libxcb-dri2.so.[0-9]+
|
||||
(.*/)*libxcb-dri3.so.[0-9]+
|
||||
(.*/)*libxcb-glx.so.[0-9]+
|
||||
(.*/)*libxcb-icccm.so.[0-9]+
|
||||
(.*/)*libxcb-image.so.[0-9]+
|
||||
(.*/)*libxcb-keysyms.so.[0-9]+
|
||||
(.*/)*libxcb-present.so.[0-9]+
|
||||
(.*/)*libxcb-randr.so.[0-9]+
|
||||
(.*/)*libxcb-render-util.so.[0-9]+
|
||||
(.*/)*libxcb-render.so.[0-9]+
|
||||
(.*/)*libxcb-shape.so.[0-9]+
|
||||
(.*/)*libxcb-shm.so.[0-9]+
|
||||
(.*/)*libxcb-sync.so.[0-9]+
|
||||
(.*/)*libxcb-util.so.[0-9]+
|
||||
(.*/)*libxcb-xfixes.so.[0-9]+
|
||||
(.*/)*libxcb-xinerama.so.[0-9]+
|
||||
(.*/)*libxcb-xkb.so.[0-9]+
|
||||
(.*/)*libxcb.so.[0-9]+
|
||||
(.*/)*libxkbcommon-x11.so.[0-9]+
|
||||
(.*/)*libxkbcommon.so.[0-9]+
|
||||
(.*/)*libxshmfence.so.[0-9]+
|
||||
|
||||
# OpenGL
|
||||
(.*/)*libdrm.so.2
|
||||
(.*/)*libgbm.so.1
|
||||
(.*/)*libEGL.so.1
|
||||
(.*/)*libGL.so.1
|
||||
(.*/)*libGLX.so.0
|
||||
(.*/)*libGLdispatch.so.0
|
||||
|
||||
# Use system library instead
|
||||
(.*/)*libasound.so.2
|
||||
(.*/)*libpulse.so.0
|
||||
(.*/)*libpulse-simple.so.0
|
||||
(.*/)*libjack.so.0
|
||||
(.*/)*libv4l2.so.0
|
110
ports/deploy/exclude.posix.linux.txt
Normal file
110
ports/deploy/exclude.posix.linux.txt
Normal file
|
@ -0,0 +1,110 @@
|
|||
# VDSO
|
||||
(.*/)*ld-linux.so.2
|
||||
(.*/)*ld-linux-x86-64.so.2
|
||||
|
||||
# Glibc
|
||||
(.*/)*libc.so.[0-9]+
|
||||
(.*/)*libdl.so.[0-9]+
|
||||
(.*/)*libm.so.[0-9]+
|
||||
(.*/)*libmvec.so.[0-9]+
|
||||
(.*/)*libpthread.so.[0-9]+
|
||||
(.*/)*libresolv.so.[0-9]+
|
||||
(.*/)*librt.so.[0-9]+
|
||||
|
||||
# GCC
|
||||
(.*/)*libgcc_s.so.[0-9]+
|
||||
(.*/)*libgomp.so.[0-9]+
|
||||
(.*/)*libstdc\+\+.so.[0-9]+
|
||||
|
||||
# Core libraries
|
||||
(.*/)*libSM.so.6
|
||||
(.*/)*libblkid.so.1
|
||||
(.*/)*libcap.so.2
|
||||
(.*/)*libcom_err.so.2
|
||||
(.*/)*libcrypto.so.1.0.0
|
||||
(.*/)*libdb-5.3.so
|
||||
(.*/)*libdbus-1.so.3
|
||||
(.*/)*libexpat.so.1
|
||||
(.*/)*libfontconfig.so.1
|
||||
(.*/)*libfreetype.so.6
|
||||
(.*/)*libgcrypt.so.20
|
||||
(.*/)*libgmp.so.10
|
||||
(.*/)*libgpg-error.so.0
|
||||
(.*/)*libgssapi_krb5.so.2
|
||||
(.*/)*libharfbuzz.so.0
|
||||
(.*/)*libk5crypto.so.3
|
||||
(.*/)*libkeyutils.so.1
|
||||
(.*/)*libkrb5.so.3
|
||||
(.*/)*libkrb5support.so.0
|
||||
(.*/)*liblz4.so.1
|
||||
(.*/)*liblzma.so.5
|
||||
(.*/)*libmount.so.1
|
||||
(.*/)*libpcre.so.[0-9]+$
|
||||
(.*/)*libpcre16.so.0
|
||||
(.*/)*libssh2.so.1
|
||||
(.*/)*libssl.so.1.0.0
|
||||
(.*/)*libtasn1.so.6
|
||||
(.*/)*libusb-1.0.so.0
|
||||
(.*/)*libuuid.so.1
|
||||
(.*/)*libz.so.1
|
||||
|
||||
# Glib2
|
||||
(.*/)*libgio-2.0.so.0
|
||||
(.*/)*libglib-2.0.so.0
|
||||
(.*/)*libgmodule-2.0.so.0
|
||||
(.*/)*libgobject-2.0.so.0
|
||||
|
||||
# X11
|
||||
(.*/)*libX11-xcb.so.[0-9]+
|
||||
(.*/)*libX11.so.[0-9]+
|
||||
(.*/)*libXau.so.[0-9]+
|
||||
(.*/)*libXcursor.so.[0-9]+
|
||||
(.*/)*libXdamage.so.[0-9]+
|
||||
(.*/)*libXdmcp.so.[0-9]+
|
||||
(.*/)*libXext.so.[0-9]+
|
||||
(.*/)*libXfixes.so.[0-9]+
|
||||
(.*/)*libXi.so.[0-9]+
|
||||
(.*/)*libXinerama.so.[0-9]+
|
||||
(.*/)*libXrandr.so.[0-9]+
|
||||
(.*/)*libXrender.so.[0-9]+
|
||||
(.*/)*libXss.so.[0-9]+
|
||||
(.*/)*libXv.so.[0-9]+
|
||||
(.*/)*libXxf86vm.so.[0-9]+
|
||||
(.*/)*libglapi.so.[0-9]+
|
||||
(.*/)*libp11-kit.so.[0-9]+
|
||||
(.*/)*libxcb-dri2.so.[0-9]+
|
||||
(.*/)*libxcb-dri3.so.[0-9]+
|
||||
(.*/)*libxcb-glx.so.[0-9]+
|
||||
(.*/)*libxcb-icccm.so.[0-9]+
|
||||
(.*/)*libxcb-image.so.[0-9]+
|
||||
(.*/)*libxcb-keysyms.so.[0-9]+
|
||||
(.*/)*libxcb-present.so.[0-9]+
|
||||
(.*/)*libxcb-randr.so.[0-9]+
|
||||
(.*/)*libxcb-render-util.so.[0-9]+
|
||||
(.*/)*libxcb-render.so.[0-9]+
|
||||
(.*/)*libxcb-shape.so.[0-9]+
|
||||
(.*/)*libxcb-shm.so.[0-9]+
|
||||
(.*/)*libxcb-sync.so.[0-9]+
|
||||
(.*/)*libxcb-util.so.[0-9]+
|
||||
(.*/)*libxcb-xfixes.so.[0-9]+
|
||||
(.*/)*libxcb-xinerama.so.[0-9]+
|
||||
(.*/)*libxcb-xkb.so.[0-9]+
|
||||
(.*/)*libxcb.so.[0-9]+
|
||||
(.*/)*libxkbcommon-x11.so.[0-9]+
|
||||
(.*/)*libxkbcommon.so.[0-9]+
|
||||
(.*/)*libxshmfence.so.[0-9]+
|
||||
|
||||
# OpenGL
|
||||
(.*/)*libdrm.so.2
|
||||
(.*/)*libgbm.so.1
|
||||
(.*/)*libEGL.so.1
|
||||
(.*/)*libGL.so.1
|
||||
(.*/)*libGLX.so.0
|
||||
(.*/)*libGLdispatch.so.0
|
||||
|
||||
# Use system library instead
|
||||
(.*/)*libasound.so.2
|
||||
(.*/)*libpulse.so.0
|
||||
(.*/)*libpulse-simple.so.0
|
||||
(.*/)*libjack.so.0
|
||||
(.*/)*libv4l2.so.0
|
13
ports/deploy/installscript.mac.qs
Normal file
13
ports/deploy/installscript.mac.qs
Normal file
|
@ -0,0 +1,13 @@
|
|||
function Component()
|
||||
{
|
||||
}
|
||||
|
||||
Component.prototype.beginInstallation = function()
|
||||
{
|
||||
component.beginInstallation();
|
||||
}
|
||||
|
||||
Component.prototype.createOperations = function()
|
||||
{
|
||||
component.createOperations();
|
||||
}
|
53
ports/deploy/installscript.posix.qs
Normal file
53
ports/deploy/installscript.posix.qs
Normal file
|
@ -0,0 +1,53 @@
|
|||
function Component()
|
||||
{
|
||||
}
|
||||
|
||||
Component.prototype.beginInstallation = function()
|
||||
{
|
||||
component.beginInstallation();
|
||||
}
|
||||
|
||||
Component.prototype.createOperations = function()
|
||||
{
|
||||
component.createOperations();
|
||||
|
||||
component.addOperation("InstallIcons", "@TargetDir@/share/icons" );
|
||||
component.addOperation("CreateDesktopEntry",
|
||||
"Webcamoid.desktop",
|
||||
"Name=Webcamoid\n"
|
||||
+ "GenericName=Webcam Capture Software\n"
|
||||
+ "GenericName[ca]=Programari de Captura de Càmera web\n"
|
||||
+ "GenericName[de]=Webcam-Capture-Software\n"
|
||||
+ "GenericName[el]=κάμερα συλλαμβάνει το λογισμικό\n"
|
||||
+ "GenericName[es]=Programa para Captura de la Webcam\n"
|
||||
+ "GenericName[fr]=Logiciel de Capture Webcam\n"
|
||||
+ "GenericName[gl]=Programa de Captura de Webcam\n"
|
||||
+ "GenericName[it]=Webcam Capture Software\n"
|
||||
+ "GenericName[ja]=ウェブカメラのキャプチャソフトウェア\n"
|
||||
+ "GenericName[ko]=웹캠 캡처 소프트웨어\n"
|
||||
+ "GenericName[pt]=Software de Captura de Webcam\n"
|
||||
+ "GenericName[ru]=Веб-камера захвата программного обеспечения\n"
|
||||
+ "GenericName[zh_CN]=摄像头捕捉软件\n"
|
||||
+ "GenericName[zh_TW]=攝像頭捕捉軟件\n"
|
||||
+ "Comment=Take photos and record videos with your webcam\n"
|
||||
+ "Comment[ca]=Fer fotos i gravar vídeos amb la seva webcam\n"
|
||||
+ "Comment[de]=Maak foto's en video's opnemen met uw webcam\n"
|
||||
+ "Comment[el]=Τραβήξτε φωτογραφίες και εγγραφή βίντεο με την κάμερα σας\n"
|
||||
+ "Comment[es]=Tome fotos y grabe videos con su camara web\n"
|
||||
+ "Comment[fr]=Prenez des photos et enregistrer des vidéos avec votre webcam\n"
|
||||
+ "Comment[gl]=Facer fotos e gravar vídeos coa súa cámara web\n"
|
||||
+ "Comment[it]=Scatta foto e registrare video con la tua webcam\n"
|
||||
+ "Comment[ja]=ウェブカメラで写真や記録ビデオを撮影\n"
|
||||
+ "Comment[ko]=웹캠으로 사진과 기록 비디오를 촬영\n"
|
||||
+ "Comment[pt]=Tirar fotos e gravar vídeos com sua webcam\n"
|
||||
+ "Comment[ru]=Возьмите фотографии и записывать видео с веб-камеры\n"
|
||||
+ "Comment[zh_CN]=拍摄照片和录制视频与您的摄像头\n"
|
||||
+ "Comment[zh_TW]=拍攝照片和錄製視頻與您的攝像頭\n"
|
||||
+ "Keywords=photo;video;webcam;\n"
|
||||
+ "Exec=" + installer.value("RunProgram") + "\n"
|
||||
+ "Icon=webcamoid\n"
|
||||
+ "Terminal=false\n"
|
||||
+ "Type=Application\n"
|
||||
+ "Categories=AudioVideo;Player;Qt;\n"
|
||||
+ "StartupNotify=true\n");
|
||||
}
|
21
ports/deploy/installscript.windows.qs
Normal file
21
ports/deploy/installscript.windows.qs
Normal file
|
@ -0,0 +1,21 @@
|
|||
function Component()
|
||||
{
|
||||
}
|
||||
|
||||
Component.prototype.beginInstallation = function()
|
||||
{
|
||||
component.beginInstallation();
|
||||
}
|
||||
|
||||
Component.prototype.createOperations = function()
|
||||
{
|
||||
component.createOperations();
|
||||
|
||||
// Create shortcuts.
|
||||
var installDir = ["@TargetDir@", "@StartMenuDir@", "@DesktopDir@"];
|
||||
|
||||
for (var dir in installDir)
|
||||
component.addOperation("CreateShortcut",
|
||||
"@TargetDir@/bin/webcamoid.exe",
|
||||
installDir[dir] + "/webcamoid.lnk");
|
||||
}
|
7
ports/deploy/package_info.conf
Normal file
7
ports/deploy/package_info.conf
Normal file
|
@ -0,0 +1,7 @@
|
|||
[Package]
|
||||
appName = Webcamoid
|
||||
description = Webcamoid, The ultimate webcam suite!
|
||||
url = https://webcamoid.github.io/
|
||||
titleColor = #3F1F7F
|
||||
runMessage = Launch Webcamoid now!
|
||||
licenseDescription = GNU General Public License v3.0
|
199
ports/deploy/tools/android.py
Normal file
199
ports/deploy/tools/android.py
Normal file
|
@ -0,0 +1,199 @@
|
|||
#!/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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# Web-Site: http://webcamoid.github.io/
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import tools.utils
|
||||
|
||||
|
||||
class AndroidTools(tools.utils.DeployToolsUtils):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.androidPlatform = ''
|
||||
self.archMap = [('arm64-v8a' , 'aarch64', 'aarch64-linux-android'),
|
||||
('armeabi-v7a', 'arm' , 'arm-linux-androideabi'),
|
||||
('x86' , 'i686' , 'i686-linux-android' ),
|
||||
('x86_64' , 'x86_64' , 'x86_64-linux-android' )]
|
||||
self.androidSDK = ''
|
||||
self.androidNDK = ''
|
||||
self.bundledInLib = []
|
||||
self.qtLibs = []
|
||||
self.localLibs = []
|
||||
|
||||
if 'ANDROID_HOME' in os.environ:
|
||||
self.androidSDK = os.environ['ANDROID_HOME']
|
||||
|
||||
if 'ANDROID_NDK_ROOT' in os.environ:
|
||||
self.androidNDK = os.environ['ANDROID_NDK_ROOT']
|
||||
elif 'ANDROID_NDK' in os.environ:
|
||||
self.androidNDK = os.environ['ANDROID_NDK']
|
||||
|
||||
def detectAndroidPlatform(self, path=''):
|
||||
for makeFile in self.detectMakeFiles(path):
|
||||
with open(makeFile) as f:
|
||||
for line in f:
|
||||
if line.startswith('DESTDIR') and '=' in line:
|
||||
buildPath = os.path.join(path, line.split('=')[1].strip())
|
||||
chunks = [part for part in buildPath.split(os.path.sep) if len(part) > 0]
|
||||
|
||||
if len(chunks) >= 2:
|
||||
self.androidPlatform = chunks[len(chunks) - 2]
|
||||
|
||||
return
|
||||
|
||||
def detectLibPaths(self):
|
||||
if len(self.androidNDK) < 1:
|
||||
return []
|
||||
|
||||
for arch in self.archMap:
|
||||
if self.targetArch == arch[0]:
|
||||
return [os.path.join(self.androidNDK,
|
||||
'toolchains',
|
||||
'llvm',
|
||||
'prebuilt',
|
||||
'linux-x86_64',
|
||||
'sysroot',
|
||||
'usr',
|
||||
'lib',
|
||||
arch[1] + '-linux-android')]
|
||||
|
||||
return []
|
||||
|
||||
def detectBinPaths(self):
|
||||
if len(self.androidNDK) < 1:
|
||||
return []
|
||||
|
||||
for arch in self.archMap:
|
||||
if self.targetArch == arch[0]:
|
||||
binPath = os.path.join(self.androidNDK,
|
||||
'toolchains',
|
||||
'llvm',
|
||||
'prebuilt',
|
||||
'linux-x86_64',
|
||||
arch[2],
|
||||
'bin')
|
||||
|
||||
return [binPath]
|
||||
|
||||
return []
|
||||
|
||||
def fixQtLibs(self):
|
||||
for root, dirs, files in os.walk(self.assetsIntallDir):
|
||||
for f in files:
|
||||
if f.endswith('.so'):
|
||||
srcPath = os.path.join(root, f)
|
||||
relPath = root.replace(self.assetsIntallDir, '')[1:]
|
||||
prefix = 'lib' + relPath.replace(os.path.sep, '_') + '_'
|
||||
lib = ''
|
||||
|
||||
if f.startswith(prefix):
|
||||
lib = f
|
||||
else:
|
||||
lib = prefix + f
|
||||
|
||||
dstPath = os.path.join(self.libInstallDir, lib)
|
||||
print(' {} -> {}'.format(srcPath, dstPath))
|
||||
self.move(srcPath, dstPath)
|
||||
self.bundledInLib += [(lib, os.path.join(relPath, f))]
|
||||
|
||||
def libBaseName(self, lib):
|
||||
basename = os.path.basename(lib)
|
||||
|
||||
return basename[3: len(basename) - 3]
|
||||
|
||||
def fixLibsXml(self):
|
||||
bundledInAssets = []
|
||||
assetsDir = os.path.join(self.rootInstallDir, 'assets')
|
||||
|
||||
for root, dirs, files in os.walk(assetsDir):
|
||||
for f in files:
|
||||
srcPath = os.path.join(root.replace(assetsDir, '')[1:], f)
|
||||
dstPath = os.path.sep.join(srcPath.split(os.path.sep)[1:])
|
||||
|
||||
if (len(dstPath) > 0):
|
||||
bundledInAssets += [(srcPath, dstPath)]
|
||||
|
||||
libsXml = os.path.join(self.rootInstallDir, 'res', 'values', 'libs.xml')
|
||||
libsXmlTemp = os.path.join(self.rootInstallDir, 'res', 'values', 'libsTemp.xml')
|
||||
|
||||
tree = ET.parse(libsXml)
|
||||
root = tree.getroot()
|
||||
oldFeatures = set()
|
||||
oldPermissions = set()
|
||||
resources = {}
|
||||
|
||||
for array in root:
|
||||
if not array.attrib['name'] in resources:
|
||||
resources[array.attrib['name']] = set()
|
||||
|
||||
for item in array:
|
||||
if item.text:
|
||||
lib = item.text.strip()
|
||||
|
||||
if len(lib) > 0:
|
||||
lib = '<item>{}</item>'.format(lib)
|
||||
resources[array.attrib['name']].add(lib)
|
||||
|
||||
qtLibs = set(['<item>{};{}</item>'.format(self.targetArch, self.libBaseName(lib)) for lib in self.qtLibs])
|
||||
|
||||
if 'qt_libs' in resources:
|
||||
qtLibs -= resources['qt_libs']
|
||||
|
||||
qtLibs = '\n'.join(sorted(list(qtLibs)))
|
||||
bundledInLib = set(['<item>{}:{}</item>'.format(lib[0], lib[1]) for lib in self.bundledInLib])
|
||||
|
||||
if 'bundled_in_lib' in resources:
|
||||
bundledInLib -= resources['bundled_in_lib']
|
||||
|
||||
bundledInLib = '\n'.join(sorted(list(bundledInLib)))
|
||||
bundledInAssets = set(['<item>{}:{}</item>'.format(lib[0], lib[1]) for lib in bundledInAssets])
|
||||
|
||||
if 'bundled_in_assets' in resources:
|
||||
bundledInAssets -= resources['bundled_in_assets']
|
||||
|
||||
bundledInAssets = '\n'.join(sorted(list(bundledInAssets)))
|
||||
|
||||
localLibs = sorted(list(set(self.localLibs)))
|
||||
localLibs = set(['<item>{};{}</item>'.format(self.targetArch, ':'.join(localLibs)) for lib in localLibs])
|
||||
|
||||
if 'load_local_libs' in resources:
|
||||
localLibs -= resources['load_local_libs']
|
||||
|
||||
localLibs = '\n'.join(sorted(list(localLibs)))
|
||||
|
||||
replace = {'<!-- %%INSERT_EXTRA_LIBS%% -->' : '',
|
||||
'<!-- %%INSERT_QT_LIBS%% -->' : qtLibs,
|
||||
'<!-- %%INSERT_BUNDLED_IN_LIB%% -->' : bundledInLib,
|
||||
'<!-- %%INSERT_BUNDLED_IN_ASSETS%% -->': bundledInAssets,
|
||||
'<!-- %%INSERT_LOCAL_LIBS%% -->' : localLibs}
|
||||
|
||||
with open(libsXml) as inFile:
|
||||
with open(libsXmlTemp, 'w') as outFile:
|
||||
for line in inFile:
|
||||
for key in replace:
|
||||
line = line.replace(key, replace[key])
|
||||
|
||||
outFile.write(line)
|
||||
|
||||
os.remove(libsXml)
|
||||
shutil.move(libsXmlTemp, libsXml)
|
169
ports/deploy/tools/binary.py
Normal file
169
ports/deploy/tools/binary.py
Normal file
|
@ -0,0 +1,169 @@
|
|||
#!/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 os
|
||||
import re
|
||||
import subprocess # nosec
|
||||
import threading
|
||||
import time
|
||||
|
||||
import tools
|
||||
|
||||
|
||||
class DeployToolsBinary(tools.utils.DeployToolsUtils):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.detectStrip()
|
||||
self.excludes = []
|
||||
|
||||
def isValid(self, path):
|
||||
return False
|
||||
|
||||
def find(self, path):
|
||||
binaries = []
|
||||
|
||||
for root, _, files in os.walk(path):
|
||||
for f in files:
|
||||
binaryPath = os.path.join(root, f)
|
||||
|
||||
if not os.path.islink(binaryPath) and self.isValid(binaryPath):
|
||||
binaries.append(binaryPath)
|
||||
|
||||
return binaries
|
||||
|
||||
def dump(self, binary):
|
||||
return {}
|
||||
|
||||
def dependencies(self, binary):
|
||||
return []
|
||||
|
||||
def allDependencies(self, binary):
|
||||
deps = self.dependencies(binary)
|
||||
solved = set()
|
||||
|
||||
while len(deps) > 0:
|
||||
dep = deps.pop()
|
||||
|
||||
for binDep in self.dependencies(dep):
|
||||
if binDep != dep and not binDep in solved:
|
||||
deps.append(binDep)
|
||||
|
||||
if self.system == 'mac':
|
||||
i = dep.rfind('.framework/')
|
||||
|
||||
if i >= 0:
|
||||
dep = dep[: i] + '.framework'
|
||||
|
||||
solved.add(dep)
|
||||
|
||||
return solved
|
||||
|
||||
def scanDependencies(self, path):
|
||||
deps = set()
|
||||
|
||||
for binPath in self.find(path):
|
||||
for dep in self.allDependencies(binPath):
|
||||
deps.add(dep)
|
||||
|
||||
return sorted(deps)
|
||||
|
||||
def name(self, binary):
|
||||
return ''
|
||||
|
||||
def detectStrip(self):
|
||||
self.stripBin = self.whereBin('strip.exe' if self.system == 'windows' else 'strip')
|
||||
|
||||
def strip(self, binary):
|
||||
if self.stripBin == '':
|
||||
return
|
||||
|
||||
process = subprocess.Popen([self.stripBin, binary], # nosec
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
process.communicate()
|
||||
|
||||
def stripSymbols(self, path):
|
||||
threads = []
|
||||
|
||||
for binary in self.find(path):
|
||||
thread = threading.Thread(target=self.strip, args=(binary,))
|
||||
threads.append(thread)
|
||||
|
||||
while threading.active_count() >= self.njobs:
|
||||
time.sleep(0.25)
|
||||
|
||||
thread.start()
|
||||
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
def readExcludeList(self, excludeList):
|
||||
self.excludes = []
|
||||
|
||||
if os.path.exists(excludeList):
|
||||
with open(excludeList) as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
|
||||
if len(line) > 0 and line[0] != '#':
|
||||
i = line.find('#')
|
||||
|
||||
if i >= 0:
|
||||
line = line[: i]
|
||||
|
||||
line = line.strip()
|
||||
|
||||
if len(line) > 0:
|
||||
self.excludes.append(line)
|
||||
|
||||
def isExcluded(self, path):
|
||||
for exclude in self.excludes:
|
||||
if self.targetSystem == 'windows' or self.targetSystem == 'posix_windows':
|
||||
path = path.lower().replace('\\', '/')
|
||||
exclude = exclude.lower()
|
||||
|
||||
if re.fullmatch(exclude, path):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def resetFilePermissions(self, rootPath, binariesPath):
|
||||
for root, dirs, files in os.walk(rootPath):
|
||||
for d in dirs:
|
||||
permissions = 0o755
|
||||
path = os.path.join(root, d)
|
||||
|
||||
if self.system == 'mac':
|
||||
os.chmod(path, permissions, follow_symlinks=False)
|
||||
else:
|
||||
os.chmod(path, permissions)
|
||||
|
||||
for f in files:
|
||||
permissions = 0o644
|
||||
path = os.path.join(root, f)
|
||||
|
||||
if root == binariesPath and self.isValid(path):
|
||||
permissions = 0o744
|
||||
|
||||
if self.system == 'mac':
|
||||
os.chmod(path, permissions, follow_symlinks=False)
|
||||
else:
|
||||
os.chmod(path, permissions)
|
345
ports/deploy/tools/binary_elf.py
Normal file
345
ports/deploy/tools/binary_elf.py
Normal file
|
@ -0,0 +1,345 @@
|
|||
#!/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 fnmatch
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import sys
|
||||
|
||||
import tools.binary
|
||||
|
||||
|
||||
class DeployToolsBinary(tools.binary.DeployToolsBinary):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.ldLibraryPath = os.environ['LD_LIBRARY_PATH'].split(':') if 'LD_LIBRARY_PATH' in os.environ else []
|
||||
self.libsSeachPaths = self.readLdconf() \
|
||||
+ ['/usr/lib',
|
||||
'/usr/lib64',
|
||||
'/lib',
|
||||
'/lib64',
|
||||
'/usr/local/lib',
|
||||
'/usr/local/lib64']
|
||||
self.emCodes = {3 : '386',
|
||||
40 : 'ARM',
|
||||
62 : 'X86_64',
|
||||
183: 'AARCH64'}
|
||||
|
||||
def readLdconf(self, ldconf='/etc/ld.so.conf'):
|
||||
if not os.path.exists(ldconf):
|
||||
return []
|
||||
|
||||
confDir = os.path.dirname(ldconf)
|
||||
libpaths = []
|
||||
|
||||
with open(ldconf) as f:
|
||||
for line in f:
|
||||
i = line.find('#')
|
||||
|
||||
if i == 0:
|
||||
continue
|
||||
|
||||
if i >= 0:
|
||||
line = line[: i]
|
||||
|
||||
line = line.strip()
|
||||
|
||||
if len(line) < 1:
|
||||
continue
|
||||
|
||||
if line.startswith('include'):
|
||||
conf = line.split()[1]
|
||||
|
||||
if not conf.startswith('/'):
|
||||
conf = os.path.join(confDir, conf)
|
||||
|
||||
dirname = os.path.dirname(conf)
|
||||
|
||||
if os.path.exists(dirname):
|
||||
for f in os.listdir(dirname):
|
||||
path = os.path.join(dirname, f)
|
||||
|
||||
if fnmatch.fnmatch(path, conf):
|
||||
libpaths += self.readLdconf(path)
|
||||
else:
|
||||
libpaths.append(line)
|
||||
|
||||
return libpaths
|
||||
|
||||
def isValid(self, path):
|
||||
with open(path, 'rb') as f:
|
||||
return f.read(4) == b'\x7fELF'
|
||||
|
||||
@staticmethod
|
||||
def readString(f):
|
||||
s = b''
|
||||
|
||||
while True:
|
||||
c = f.read(1)
|
||||
|
||||
if c == b'\x00':
|
||||
break
|
||||
|
||||
s += c
|
||||
|
||||
return s
|
||||
|
||||
@staticmethod
|
||||
def readNumber(f, arch):
|
||||
if arch == '32bits':
|
||||
return struct.unpack('I', f.read(4))[0]
|
||||
|
||||
return struct.unpack('Q', f.read(8))[0]
|
||||
|
||||
@staticmethod
|
||||
def readDynamicEntry(f, arch):
|
||||
if arch == '32bits':
|
||||
return struct.unpack('iI', f.read(8))
|
||||
|
||||
return struct.unpack('qQ', f.read(16))
|
||||
|
||||
# https://refspecs.linuxfoundation.org/lsb.shtml (See Core, Generic)
|
||||
# https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
|
||||
def dump(self, binary):
|
||||
# ELF file magic
|
||||
ELFMAGIC = b'\x7fELF'
|
||||
|
||||
# Sections
|
||||
SHT_STRTAB = 0x3
|
||||
SHT_DYNAMIC = 0x6
|
||||
|
||||
# Dynamic section entries
|
||||
DT_NULL = 0
|
||||
DT_NEEDED = 1
|
||||
DT_RPATH = 15
|
||||
DT_RUNPATH = 0x1d
|
||||
|
||||
with open(binary, 'rb') as f:
|
||||
# Read magic signature.
|
||||
magic = f.read(4)
|
||||
|
||||
if magic != ELFMAGIC:
|
||||
return {}
|
||||
|
||||
# Read the data structure of the file.
|
||||
eiClass = '32bits' if struct.unpack('B', f.read(1))[0] == 1 else '64bits'
|
||||
|
||||
# Read machine code.
|
||||
f.seek(0x12, os.SEEK_SET)
|
||||
machine = struct.unpack('H', f.read(2))[0]
|
||||
|
||||
# Get a pointer to the sections table.
|
||||
sectionHeaderTable = 0
|
||||
|
||||
if eiClass == '32bits':
|
||||
f.seek(0x20, os.SEEK_SET)
|
||||
sectionHeaderTable = self.readNumber(f, eiClass)
|
||||
f.seek(0x30, os.SEEK_SET)
|
||||
else:
|
||||
f.seek(0x28, os.SEEK_SET)
|
||||
sectionHeaderTable = self.readNumber(f, eiClass)
|
||||
f.seek(0x3c, os.SEEK_SET)
|
||||
|
||||
# Read the number of sections.
|
||||
nSections = struct.unpack('H', f.read(2))[0]
|
||||
|
||||
# Read the index of the string table that stores sections names.
|
||||
shstrtabIndex = struct.unpack('H', f.read(2))[0]
|
||||
|
||||
# Read sections.
|
||||
f.seek(sectionHeaderTable, os.SEEK_SET)
|
||||
neededPtr = []
|
||||
rpathsPtr = []
|
||||
runpathsPtr = []
|
||||
strtabs = []
|
||||
shstrtab = []
|
||||
|
||||
for section in range(nSections):
|
||||
sectionStart = f.tell()
|
||||
|
||||
# Read the a pointer to the virtual address in the string table
|
||||
# that contains the name of this section.
|
||||
sectionName = struct.unpack('I', f.read(4))[0]
|
||||
|
||||
# Read the type of this section.
|
||||
sectionType = struct.unpack('I', f.read(4))[0]
|
||||
|
||||
# Read the virtual address of this section.
|
||||
f.seek(sectionStart + (0x0c if eiClass == '32bits' else 0x10), os.SEEK_SET)
|
||||
shAddr = self.readNumber(f, eiClass)
|
||||
|
||||
# Read the offset in file to this section.
|
||||
shOffset = self.readNumber(f, eiClass)
|
||||
f.seek(shOffset, os.SEEK_SET)
|
||||
|
||||
if sectionType == SHT_DYNAMIC:
|
||||
# Read dynamic sections.
|
||||
while True:
|
||||
# Read dynamic entries.
|
||||
dTag, dVal = self.readDynamicEntry(f, eiClass)
|
||||
|
||||
if dTag == DT_NULL:
|
||||
# End of dynamic sections.
|
||||
break
|
||||
elif dTag == DT_NEEDED:
|
||||
# Dynamically imported libraries.
|
||||
neededPtr.append(dVal)
|
||||
elif dTag == DT_RPATH:
|
||||
# RPATHs.
|
||||
rpathsPtr.append(dVal)
|
||||
elif dTag == DT_RUNPATH:
|
||||
# RUNPATHs.
|
||||
runpathsPtr.append(dVal)
|
||||
elif sectionType == SHT_STRTAB:
|
||||
# Read string tables.
|
||||
if section == shstrtabIndex:
|
||||
# We found the string table that stores sections names.
|
||||
shstrtab = [shAddr, shOffset]
|
||||
else:
|
||||
# Save string tables for later usage.
|
||||
strtabs += [[sectionName, shAddr, shOffset]]
|
||||
|
||||
# Move to next section.
|
||||
f.seek(sectionStart + (0x28 if eiClass == '32bits' else 0x40), os.SEEK_SET)
|
||||
|
||||
# Libraries names and RUNPATHs are located in '.dynstr' table.
|
||||
strtab = []
|
||||
|
||||
for tab in strtabs:
|
||||
f.seek(tab[0] - shstrtab[0] + shstrtab[1], os.SEEK_SET)
|
||||
|
||||
if self.readString(f) == b'.dynstr':
|
||||
strtab = tab
|
||||
|
||||
# Read dynamically imported libraries.
|
||||
needed = set()
|
||||
|
||||
for lib in neededPtr:
|
||||
f.seek(lib + strtab[2], os.SEEK_SET)
|
||||
needed.add(self.readString(f).decode(sys.getdefaultencoding()))
|
||||
|
||||
# Read RPATHs
|
||||
rpaths = set()
|
||||
|
||||
for path in rpathsPtr:
|
||||
f.seek(path + strtab[2], os.SEEK_SET)
|
||||
rpaths.add(self.readString(f).decode(sys.getdefaultencoding()))
|
||||
|
||||
# Read RUNPATHs
|
||||
runpaths = set()
|
||||
|
||||
for path in runpathsPtr:
|
||||
f.seek(path + strtab[2], os.SEEK_SET)
|
||||
runpaths.add(self.readString(f).decode(sys.getdefaultencoding()))
|
||||
|
||||
return {'machine': machine,
|
||||
'imports': needed,
|
||||
'rpath': rpaths,
|
||||
'runpath': runpaths}
|
||||
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def readRpaths(elfInfo, binDir):
|
||||
rpaths = []
|
||||
runpaths = []
|
||||
|
||||
# http://amir.rachum.com/blog/2016/09/17/shared-libraries/
|
||||
for rpath in ['rpath', 'runpath']:
|
||||
for path in elfInfo[rpath]:
|
||||
if '$ORIGIN' in path:
|
||||
path = path.replace('$ORIGIN', binDir)
|
||||
|
||||
if not path.startswith('/'):
|
||||
path = os.path.join(binDir, path)
|
||||
|
||||
path = os.path.normpath(path)
|
||||
|
||||
if rpath == 'rpath':
|
||||
rpaths.append(path)
|
||||
else:
|
||||
runpaths.append(path)
|
||||
|
||||
return rpaths, runpaths
|
||||
|
||||
def libPath(self, lib, machine, rpaths, runpaths):
|
||||
# man ld.so
|
||||
searchPaths = rpaths \
|
||||
+ self.ldLibraryPath \
|
||||
+ runpaths \
|
||||
+ self.libsSeachPaths
|
||||
|
||||
for libdir in searchPaths:
|
||||
path = os.path.join(libdir, lib)
|
||||
|
||||
if os.path.exists(path):
|
||||
depElfInfo = self.dump(path)
|
||||
|
||||
if depElfInfo:
|
||||
if 'machine' in depElfInfo and (machine == 0 or depElfInfo['machine'] == machine):
|
||||
return path
|
||||
elif 'links' in depElfInfo and len(depElfInfo['links']) > 0:
|
||||
return path
|
||||
|
||||
return ''
|
||||
|
||||
def dependencies(self, binary):
|
||||
elfInfo = self.dump(binary)
|
||||
|
||||
if not elfInfo:
|
||||
return []
|
||||
|
||||
rpaths, runpaths = self.readRpaths(elfInfo, os.path.dirname(binary))
|
||||
libs = []
|
||||
deps = []
|
||||
|
||||
if 'imports' in elfInfo:
|
||||
deps = elfInfo['imports']
|
||||
elif 'links' in elfInfo:
|
||||
deps = elfInfo['links']
|
||||
|
||||
machine = 0
|
||||
|
||||
if 'machine' in elfInfo:
|
||||
machine = elfInfo['machine']
|
||||
|
||||
for lib in deps:
|
||||
libpath = self.libPath(lib, machine, rpaths, runpaths)
|
||||
|
||||
if len(libpath) > 0 and not self.isExcluded(libpath):
|
||||
libs.append(libpath)
|
||||
|
||||
return libs
|
||||
|
||||
def name(self, binary):
|
||||
dep = os.path.basename(binary)[3:]
|
||||
|
||||
return dep[: dep.find('.')]
|
||||
|
||||
def machineEMCode(self, binary):
|
||||
info = self.dump(binary)
|
||||
|
||||
if 'machine' in info:
|
||||
if info['machine'] in self.emCodes:
|
||||
return self.emCodes[info['machine']]
|
||||
|
||||
return 'UNKNOWN'
|
182
ports/deploy/tools/binary_mach.py
Normal file
182
ports/deploy/tools/binary_mach.py
Normal file
|
@ -0,0 +1,182 @@
|
|||
#!/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 os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
import tools.binary
|
||||
|
||||
|
||||
class DeployToolsBinary(tools.binary.DeployToolsBinary):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
# 32 bits magic number.
|
||||
self.MH_MAGIC = 0xfeedface # Native endian
|
||||
self.MH_CIGAM = 0xcefaedfe # Reverse endian
|
||||
|
||||
# 64 bits magic number.
|
||||
self.MH_MAGIC_64 = 0xfeedfacf # Native endian
|
||||
self.MH_CIGAM_64 = 0xcffaedfe # Reverse endian
|
||||
|
||||
def isValid(self, path):
|
||||
try:
|
||||
with open(path, 'rb') as f:
|
||||
# Read magic number.
|
||||
magic = struct.unpack('I', f.read(4))[0]
|
||||
|
||||
if magic == self.MH_MAGIC \
|
||||
or magic == self.MH_CIGAM \
|
||||
or magic == self.MH_MAGIC_64 \
|
||||
or magic == self.MH_CIGAM_64:
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
# https://github.com/aidansteele/osx-abi-macho-file-format-reference
|
||||
def dump(self, binary):
|
||||
# Commands definitions
|
||||
LC_REQ_DYLD = 0x80000000
|
||||
LC_LOAD_DYLIB = 0xc
|
||||
LC_RPATH = 0x1c | LC_REQ_DYLD
|
||||
LC_ID_DYLIB = 0xd
|
||||
|
||||
dylibImports = []
|
||||
rpaths = []
|
||||
dylibId = ''
|
||||
|
||||
with open(binary, 'rb') as f:
|
||||
# Read magic number.
|
||||
magic = struct.unpack('I', f.read(4))[0]
|
||||
|
||||
if magic == self.MH_MAGIC or magic == self.MH_CIGAM:
|
||||
is32bits = True
|
||||
elif magic == self.MH_MAGIC_64 or magic == self.MH_CIGAM_64:
|
||||
is32bits = False
|
||||
else:
|
||||
return {}
|
||||
|
||||
# Read number of commands.
|
||||
f.seek(12, os.SEEK_CUR)
|
||||
ncmds = struct.unpack('I', f.read(4))[0]
|
||||
|
||||
# Move to load commands
|
||||
f.seek(8 if is32bits else 12, os.SEEK_CUR)
|
||||
|
||||
for _ in range(ncmds):
|
||||
# Read a load command and store it's position in the file.
|
||||
loadCommandStart = f.tell()
|
||||
loadCommand = struct.unpack('II', f.read(8))
|
||||
|
||||
# If the command list a library
|
||||
if loadCommand[0] in [LC_LOAD_DYLIB, LC_RPATH, LC_ID_DYLIB]:
|
||||
# Save the position of the next command.
|
||||
nextCommand = f.tell() + loadCommand[1] - 8
|
||||
|
||||
# Move to the string
|
||||
f.seek(loadCommandStart + struct.unpack('I', f.read(4))[0], os.SEEK_SET)
|
||||
dylib = b''
|
||||
|
||||
# Read string until null character.
|
||||
while True:
|
||||
c = f.read(1)
|
||||
|
||||
if c == b'\x00':
|
||||
break
|
||||
|
||||
dylib += c
|
||||
s = dylib.decode(sys.getdefaultencoding())
|
||||
|
||||
if loadCommand[0] == LC_LOAD_DYLIB:
|
||||
dylibImports.append(s)
|
||||
elif loadCommand[0] == LC_RPATH:
|
||||
rpaths.append(s)
|
||||
elif loadCommand[0] == LC_ID_DYLIB:
|
||||
dylibId = s
|
||||
|
||||
f.seek(nextCommand, os.SEEK_SET)
|
||||
else:
|
||||
f.seek(loadCommand[1] - 8, os.SEEK_CUR)
|
||||
|
||||
return {'imports': dylibImports, 'rpaths': rpaths, 'id': dylibId}
|
||||
|
||||
@staticmethod
|
||||
def solveRefpath(path):
|
||||
if not path.startswith('@'):
|
||||
return path
|
||||
|
||||
searchPaths = []
|
||||
|
||||
if 'DYLD_LIBRARY_PATH' in os.environ:
|
||||
searchPaths += os.environ['DYLD_LIBRARY_PATH'].split(':')
|
||||
|
||||
if 'DYLD_FRAMEWORK_PATH' in os.environ:
|
||||
searchPaths += os.environ['DYLD_FRAMEWORK_PATH'].split(':')
|
||||
|
||||
if path.endswith('.dylib'):
|
||||
dep = os.path.basename(path)
|
||||
else:
|
||||
i = path.rfind(os.sep, 0, path.rfind('.framework'))
|
||||
dep = path[i + 1:]
|
||||
|
||||
for fpath in searchPaths:
|
||||
realPath = os.path.join(fpath, dep)
|
||||
|
||||
if os.path.exists(realPath):
|
||||
return realPath
|
||||
|
||||
return ''
|
||||
|
||||
def dependencies(self, binary):
|
||||
machInfo = self.dump(binary)
|
||||
|
||||
if not machInfo:
|
||||
return []
|
||||
|
||||
libs = []
|
||||
|
||||
for mach in machInfo['imports']:
|
||||
mach = self.solveRefpath(mach)
|
||||
|
||||
if mach == '' or self.isExcluded(mach) or not os.path.exists(mach):
|
||||
continue
|
||||
|
||||
dirName = os.path.dirname(mach)
|
||||
dirName = os.path.realpath(dirName)
|
||||
baseName = os.path.basename(mach)
|
||||
libs.append(os.path.join(dirName, baseName))
|
||||
|
||||
return libs
|
||||
|
||||
def name(self, binary):
|
||||
dep = os.path.basename(binary)
|
||||
i = dep.find('.')
|
||||
|
||||
if i >= 0:
|
||||
dep = dep[: dep.find('.')]
|
||||
|
||||
if 'Qt' in dep and not 'Qt5' in dep:
|
||||
dep = dep.replace('Qt', 'Qt5')
|
||||
|
||||
return dep
|
179
ports/deploy/tools/binary_pecoff.py
Normal file
179
ports/deploy/tools/binary_pecoff.py
Normal file
|
@ -0,0 +1,179 @@
|
|||
#!/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 mimetypes
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
import tools.binary
|
||||
|
||||
|
||||
class DeployToolsBinary(tools.binary.DeployToolsBinary):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def isValid(self, path):
|
||||
mimetype, _ = mimetypes.guess_type(path)
|
||||
|
||||
if mimetype == 'application/x-msdownload':
|
||||
return True
|
||||
|
||||
if mimetype != 'application/octet-stream':
|
||||
return False
|
||||
|
||||
with open(path, 'rb') as f:
|
||||
if f.read(2) != b'MZ':
|
||||
return []
|
||||
|
||||
f.seek(0x3c, os.SEEK_SET)
|
||||
peHeaderOffset = struct.unpack('I', f.read(4))
|
||||
f.seek(peHeaderOffset[0], os.SEEK_SET)
|
||||
peSignatue = f.read(4)
|
||||
|
||||
if peSignatue != b'PE\x00\x00':
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms680547(v=vs.85).aspx
|
||||
# https://upload.wikimedia.org/wikipedia/commons/1/1b/Portable_Executable_32_bit_Structure_in_SVG_fixed.svg
|
||||
def dump(self, binary):
|
||||
dllImports = set()
|
||||
|
||||
if not os.path.exists(binary) or not os.path.isfile(binary):
|
||||
return dllImports
|
||||
|
||||
with open(binary, 'rb') as f:
|
||||
# Check DOS header signature.
|
||||
if f.read(2) != b'MZ':
|
||||
return []
|
||||
|
||||
# Move to COFF header.
|
||||
f.seek(0x3c, os.SEEK_SET)
|
||||
peHeaderOffset = struct.unpack('I', f.read(4))
|
||||
f.seek(peHeaderOffset[0], os.SEEK_SET)
|
||||
peSignatue = f.read(4)
|
||||
|
||||
# Check COFF header signature.
|
||||
if peSignatue != b'PE\x00\x00':
|
||||
return []
|
||||
|
||||
# Read COFF header.
|
||||
coffHeader = struct.unpack('HHIIIHH', f.read(20))
|
||||
nSections = coffHeader[1]
|
||||
sectionTablePos = coffHeader[5] + f.tell()
|
||||
|
||||
# Read magic signature in standard COFF fields.
|
||||
peType = 'PE32' if f.read(2) == b'\x0b\x01' else 'PE32+'
|
||||
|
||||
# Move to data directories.
|
||||
f.seek(102 if peType == 'PE32' else 118, os.SEEK_CUR)
|
||||
|
||||
# Read the import table.
|
||||
importTablePos, importTableSize = struct.unpack('II', f.read(8))
|
||||
|
||||
# Move to Sections table.
|
||||
f.seek(sectionTablePos, os.SEEK_SET)
|
||||
sections = []
|
||||
idataTablePhysical = -1
|
||||
|
||||
# Search for 'idata' section.
|
||||
for _ in range(nSections):
|
||||
# Read section.
|
||||
section = struct.unpack('8pIIIIIIHHI', f.read(40))
|
||||
sectionName = section[0].replace(b'\x00', b'')
|
||||
|
||||
# Save a reference to the sections.
|
||||
sections += [section]
|
||||
|
||||
if sectionName == b'idata' or sectionName == b'rdata':
|
||||
idataTablePhysical = section[4]
|
||||
|
||||
# If import table was defined calculate it's position in
|
||||
# the file in relation to the address given by 'idata'.
|
||||
if importTableSize > 0:
|
||||
idataTablePhysical += importTablePos - section[2]
|
||||
|
||||
if idataTablePhysical < 0:
|
||||
return []
|
||||
|
||||
# Move to 'idata' section.
|
||||
f.seek(idataTablePhysical, os.SEEK_SET)
|
||||
dllList = set()
|
||||
|
||||
# Read 'idata' directory table.
|
||||
while True:
|
||||
# Read DLL entries.
|
||||
try:
|
||||
dllImport = struct.unpack('IIIII', f.read(20))
|
||||
except:
|
||||
break
|
||||
|
||||
# Null directory entry.
|
||||
if dllImport[0] | dllImport[1] | dllImport[2] | dllImport[3] | dllImport[4] == 0:
|
||||
break
|
||||
|
||||
# Locate where is located the DLL name in relation to the
|
||||
# sections.
|
||||
for section in sections:
|
||||
if dllImport[3] >= section[2] \
|
||||
and dllImport[3] < section[1] + section[2]:
|
||||
dllList.add(dllImport[3] - section[2] + section[4])
|
||||
|
||||
break
|
||||
|
||||
for dll in dllList:
|
||||
# Move to DLL name.
|
||||
f.seek(dll, os.SEEK_SET)
|
||||
dllName = b''
|
||||
|
||||
# Read string until null character.
|
||||
while True:
|
||||
c = f.read(1)
|
||||
|
||||
if c == b'\x00':
|
||||
break
|
||||
|
||||
dllName += c
|
||||
|
||||
try:
|
||||
dllImports.add(dllName.decode(sys.getdefaultencoding()))
|
||||
except:
|
||||
pass
|
||||
|
||||
return dllImports
|
||||
|
||||
def dependencies(self, binary):
|
||||
deps = []
|
||||
|
||||
for dep in self.dump(binary):
|
||||
depPath = self.whereBin(dep)
|
||||
|
||||
if len(depPath) > 0 and not self.isExcluded(depPath):
|
||||
deps.append(depPath)
|
||||
|
||||
return deps
|
||||
|
||||
def name(self, binary):
|
||||
dep = os.path.basename(binary)
|
||||
|
||||
return dep[: dep.find('.')]
|
671
ports/deploy/tools/qt5.py
Normal file
671
ports/deploy/tools/qt5.py
Normal file
|
@ -0,0 +1,671 @@
|
|||
#!/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 json
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import shutil
|
||||
import subprocess # nosec
|
||||
import sys
|
||||
import time
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import tools.utils
|
||||
|
||||
|
||||
class DeployToolsQt(tools.utils.DeployToolsUtils):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.qmake = ''
|
||||
self.qtIFW = ''
|
||||
self.qtIFWVersion = ''
|
||||
self.qtInstallBins = ''
|
||||
self.qtInstallQml = ''
|
||||
self.qtInstallPlugins = ''
|
||||
self.qmlRootDirs = []
|
||||
self.qmlInstallDir = ''
|
||||
self.dependencies = []
|
||||
self.binarySolver = None
|
||||
self.installerConfig = ''
|
||||
|
||||
def detectQt(self, path=''):
|
||||
self.detectQmake(path)
|
||||
self.qtInstallBins = self.qmakeQuery(var='QT_INSTALL_BINS')
|
||||
self.qtInstallQml = self.qmakeQuery(var='QT_INSTALL_QML')
|
||||
self.qtInstallPlugins = self.qmakeQuery(var='QT_INSTALL_PLUGINS')
|
||||
self.detectQtIFW()
|
||||
self.detectQtIFWVersion()
|
||||
|
||||
def detectQmake(self, path=''):
|
||||
for makeFile in self.detectMakeFiles(path):
|
||||
with open(makeFile) as f:
|
||||
for line in f:
|
||||
if line.startswith('QMAKE') and '=' in line:
|
||||
self.qmake = line.split('=')[1].strip()
|
||||
|
||||
return
|
||||
|
||||
if 'QMAKE_PATH' in os.environ:
|
||||
self.qmake = os.environ['QMAKE_PATH']
|
||||
|
||||
def detectTargetBinaryFromQt5Make(self, path=''):
|
||||
for makeFile in self.detectMakeFiles(path):
|
||||
with open(makeFile) as f:
|
||||
for line in f:
|
||||
if line.startswith('TARGET') and '=' in line:
|
||||
return os.path.join(path, line.split('=')[1].strip())
|
||||
|
||||
return ''
|
||||
|
||||
def qmakeQuery(self, qmake='', var=''):
|
||||
if qmake == '':
|
||||
if 'QMAKE_PATH' in os.environ:
|
||||
qmake = os.environ['QMAKE_PATH']
|
||||
else:
|
||||
qmake = self.qmake
|
||||
|
||||
try:
|
||||
args = [qmake, '-query']
|
||||
|
||||
if var != '':
|
||||
args += [var]
|
||||
|
||||
process = subprocess.Popen(args, # nosec
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, _ = process.communicate()
|
||||
|
||||
return stdout.strip().decode(sys.getdefaultencoding())
|
||||
except:
|
||||
pass
|
||||
|
||||
return ''
|
||||
|
||||
@staticmethod
|
||||
def detectVersion(proFile):
|
||||
if 'DAILY_BUILD' in os.environ:
|
||||
return 'daily'
|
||||
|
||||
verMaj = '0'
|
||||
verMin = '0'
|
||||
verPat = '0'
|
||||
|
||||
try:
|
||||
with open(proFile) as f:
|
||||
for line in f:
|
||||
if line.startswith('VER_MAJ') and '=' in line:
|
||||
verMaj = line.split('=')[1].strip()
|
||||
elif line.startswith('VER_MIN') and '=' in line:
|
||||
verMin = line.split('=')[1].strip()
|
||||
elif line.startswith('VER_PAT') and '=' in line:
|
||||
verPat = line.split('=')[1].strip()
|
||||
except:
|
||||
pass
|
||||
|
||||
return verMaj + '.' + verMin + '.' + verPat
|
||||
|
||||
def detectQtIFW(self):
|
||||
if 'BINARYCREATOR' in os.environ:
|
||||
self.qtIFW = os.environ['BINARYCREATOR']
|
||||
|
||||
return
|
||||
|
||||
# Try official Qt binarycreator first because it is statically linked.
|
||||
|
||||
if self.targetSystem == 'windows':
|
||||
homeQt = 'C:\\Qt'
|
||||
elif self.targetSystem == 'posix_windows':
|
||||
if 'WINEPREFIX' in os.environ:
|
||||
homeQt = os.path.expanduser(os.path.join(os.environ['WINEPREFIX'],
|
||||
'drive_c/Qt'))
|
||||
else:
|
||||
homeQt = os.path.expanduser('~/.wine/drive_c/Qt')
|
||||
else:
|
||||
homeQt = os.path.expanduser('~/Qt')
|
||||
|
||||
binCreator = 'binarycreator'
|
||||
|
||||
if self.targetSystem == 'windows' or self.targetSystem == 'posix_windows':
|
||||
binCreator += '.exe'
|
||||
|
||||
for root, _, files in os.walk(homeQt):
|
||||
for f in files:
|
||||
if f == binCreator:
|
||||
self.qtIFW = os.path.join(root, f)
|
||||
|
||||
return
|
||||
|
||||
# binarycreator offered by the system is most probably dynamically
|
||||
# linked, so it's useful for test purposes only, but not recommended
|
||||
# for distribution.
|
||||
self.qtIFW = self.whereBin(binCreator)
|
||||
|
||||
def detectQtIFWVersion(self):
|
||||
self.qtIFWVersion = ''
|
||||
|
||||
if self.qtIFW == '':
|
||||
return
|
||||
|
||||
installerBase = os.path.join(os.path.dirname(self.qtIFW),
|
||||
'installerbase')
|
||||
|
||||
if self.targetSystem == 'windows' or self.targetSystem == 'posix_windows':
|
||||
installerBase += '.exe'
|
||||
|
||||
self.qtIFWVersion = '2.0.0'
|
||||
|
||||
if not os.path.exists(installerBase):
|
||||
return
|
||||
|
||||
if self.targetSystem == 'posix_windows':
|
||||
installerBase = 'Z:' + installerBase.replace('/', '\\')
|
||||
process = subprocess.Popen(['wine', # nosec
|
||||
installerBase,
|
||||
'--version'],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, _ = process.communicate(input=b'\n')
|
||||
else:
|
||||
process = subprocess.Popen([installerBase, # nosec
|
||||
'--version'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
stdout, _ = process.communicate()
|
||||
|
||||
for line in stdout.split(b'\n'):
|
||||
if b'IFW Version:' in line:
|
||||
self.qtIFWVersion = line.split(b' ')[2].replace(b'"', b'').replace(b',', b'').decode(sys.getdefaultencoding())
|
||||
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def listQmlFiles(path):
|
||||
qmlFiles = set()
|
||||
|
||||
if os.path.isfile(path):
|
||||
baseName = os.path.basename(path)
|
||||
|
||||
if baseName == 'qmldir' or path.endswith('.qml'):
|
||||
qmlFiles.add(path)
|
||||
else:
|
||||
for root, _, files in os.walk(path):
|
||||
for f in files:
|
||||
if f == 'qmldir' or f.endswith('.qml'):
|
||||
qmlFiles.add(os.path.join(root, f))
|
||||
|
||||
return list(qmlFiles)
|
||||
|
||||
@staticmethod
|
||||
def modulePath(importLine):
|
||||
imp = importLine.strip().split()
|
||||
path = imp[1].replace('.', '/')
|
||||
majorVersion = imp[2].split('.')[0]
|
||||
|
||||
if int(majorVersion) > 1:
|
||||
path += '.{}'.format(majorVersion)
|
||||
|
||||
return path
|
||||
|
||||
def scanImports(self, path):
|
||||
if not os.path.isfile(path):
|
||||
return []
|
||||
|
||||
fileName = os.path.basename(path)
|
||||
imports = set()
|
||||
|
||||
if fileName.endswith('.qml'):
|
||||
with open(path, 'rb') as f:
|
||||
for line in f:
|
||||
if re.match(b'^import \\w+' , line):
|
||||
imports.add(self.modulePath(line.strip().decode(sys.getdefaultencoding())))
|
||||
elif fileName == 'qmldir':
|
||||
with open(path, 'rb') as f:
|
||||
for line in f:
|
||||
if re.match(b'^depends ' , line):
|
||||
imports.add(self.modulePath(line.strip().decode(sys.getdefaultencoding())))
|
||||
|
||||
return list(imports)
|
||||
|
||||
def solvedepsQml(self):
|
||||
qmlFiles = set()
|
||||
|
||||
for path in self.qmlRootDirs:
|
||||
path = os.path.join(self.rootDir, path)
|
||||
|
||||
for f in self.listQmlFiles(path):
|
||||
qmlFiles.add(f)
|
||||
|
||||
solved = set()
|
||||
solvedImports = set()
|
||||
|
||||
while len(qmlFiles) > 0:
|
||||
qmlFile = qmlFiles.pop()
|
||||
|
||||
for imp in self.scanImports(qmlFile):
|
||||
if imp in solvedImports:
|
||||
continue
|
||||
|
||||
sysModulePath = os.path.join(self.qtInstallQml, imp)
|
||||
installModulePath = os.path.join(self.qmlInstallDir, imp)
|
||||
|
||||
if os.path.exists(sysModulePath):
|
||||
print(' {} -> {}'.format(sysModulePath, installModulePath))
|
||||
self.copy(sysModulePath, installModulePath)
|
||||
solvedImports.add(imp)
|
||||
self.dependencies.append(os.path.join(sysModulePath, 'qmldir'))
|
||||
|
||||
for f in self.listQmlFiles(sysModulePath):
|
||||
if not f in solved:
|
||||
qmlFiles.add(f)
|
||||
|
||||
solved.add(qmlFile)
|
||||
|
||||
def solvedepsPlugins(self):
|
||||
pluginsMap = {
|
||||
'Qt53DRenderer': ['sceneparsers', 'geometryloaders'],
|
||||
'Qt53DQuickRenderer': ['renderplugins'],
|
||||
'Qt5Declarative': ['qml1tooling'],
|
||||
'Qt5EglFSDeviceIntegration': ['egldeviceintegrations'],
|
||||
'Qt5Gui': ['accessible',
|
||||
'generic',
|
||||
'iconengines',
|
||||
'imageformats',
|
||||
'platforms',
|
||||
'platforminputcontexts',
|
||||
'styles'],
|
||||
'Qt5Location': ['geoservices'],
|
||||
'Qt5Multimedia': ['audio', 'mediaservice', 'playlistformats'],
|
||||
'Qt5Network': ['bearer'],
|
||||
'Qt5Positioning': ['position'],
|
||||
'Qt5PrintSupport': ['printsupport'],
|
||||
'Qt5QmlTooling': ['qmltooling'],
|
||||
'Qt5Quick': ['scenegraph', 'qmltooling'],
|
||||
'Qt5Sensors': ['sensors', 'sensorgestures'],
|
||||
'Qt5SerialBus': ['canbus'],
|
||||
'Qt5Sql': ['sqldrivers'],
|
||||
'Qt5TextToSpeech': ['texttospeech'],
|
||||
'Qt5WebEngine': ['qtwebengine'],
|
||||
'Qt5WebEngineCore': ['qtwebengine'],
|
||||
'Qt5WebEngineWidgets': ['qtwebengine'],
|
||||
'Qt5WebView': ['webview'],
|
||||
'Qt5XcbQpa': ['xcbglintegrations']
|
||||
}
|
||||
|
||||
pluginsMap.update({lib + 'd': pluginsMap[lib] for lib in pluginsMap})
|
||||
|
||||
if self.targetSystem == 'android':
|
||||
pluginsMap.update({lib + '_' + self.targetArch: pluginsMap[lib] for lib in pluginsMap})
|
||||
|
||||
plugins = []
|
||||
|
||||
for dep in self.binarySolver.scanDependencies(self.installDir):
|
||||
libName = self.binarySolver.name(dep)
|
||||
|
||||
if not libName in pluginsMap:
|
||||
continue
|
||||
|
||||
for plugin in pluginsMap[libName]:
|
||||
if not plugin in plugins:
|
||||
sysPluginPath = os.path.join(self.qtInstallPlugins, plugin)
|
||||
pluginPath = os.path.join(self.pluginsInstallDir, plugin)
|
||||
|
||||
if not os.path.exists(sysPluginPath):
|
||||
continue
|
||||
|
||||
print(' {} -> {}'.format(sysPluginPath, pluginPath))
|
||||
self.copy(sysPluginPath, pluginPath)
|
||||
plugins.append(plugin)
|
||||
self.dependencies.append(sysPluginPath)
|
||||
|
||||
def solvedepsAndroid(self):
|
||||
installPrefix = self.qmakeQuery(var='QT_INSTALL_PREFIX')
|
||||
qtLibsPath = self.qmakeQuery(var='QT_INSTALL_LIBS')
|
||||
jars = []
|
||||
permissions = set()
|
||||
features = set()
|
||||
initClasses = set()
|
||||
libs = set()
|
||||
|
||||
for f in os.listdir(self.libInstallDir):
|
||||
basename = os.path.basename(f)[3:]
|
||||
basename = os.path.splitext(basename)[0]
|
||||
depFile = os.path.join(qtLibsPath,
|
||||
basename + '-android-dependencies.xml')
|
||||
|
||||
if os.path.exists(depFile):
|
||||
tree = ET.parse(depFile)
|
||||
root = tree.getroot()
|
||||
|
||||
for jar in root.iter('jar'):
|
||||
jars.append(jar.attrib['file'])
|
||||
|
||||
if 'initClass' in jar.attrib:
|
||||
initClasses.append(jar.attrib['initClass'])
|
||||
|
||||
for permission in root.iter('permission'):
|
||||
permissions.add(permission.attrib['name'])
|
||||
|
||||
for feature in root.iter('feature'):
|
||||
features.add(feature.attrib['name'])
|
||||
|
||||
for lib in root.iter('lib'):
|
||||
if 'file' in lib.attrib:
|
||||
libs.add(lib.attrib['file'])
|
||||
|
||||
self.localLibs = [os.path.basename(lib) for lib in libs]
|
||||
|
||||
print('Copying jar files\n')
|
||||
|
||||
for jar in sorted(jars):
|
||||
srcPath = os.path.join(installPrefix, jar)
|
||||
dstPath = os.path.join(self.rootInstallDir,
|
||||
'libs',
|
||||
os.path.basename(jar))
|
||||
print(' {} -> {}'.format(srcPath, dstPath))
|
||||
self.copy(srcPath, dstPath)
|
||||
|
||||
manifest = os.path.join(self.rootInstallDir, 'AndroidManifest.xml')
|
||||
manifestTemp = os.path.join(self.rootInstallDir, 'AndroidManifestTemp.xml')
|
||||
tree = ET.parse(manifest)
|
||||
root = tree.getroot()
|
||||
oldFeatures = set()
|
||||
oldPermissions = set()
|
||||
|
||||
for element in root:
|
||||
if element.tag == 'uses-feature':
|
||||
for key in element.attrib:
|
||||
if key.endswith('name'):
|
||||
oldFeatures.add(element.attrib[key])
|
||||
elif element.tag == 'uses-permission':
|
||||
for key in element.attrib:
|
||||
if key.endswith('name'):
|
||||
oldPermissions.add(element.attrib[key])
|
||||
|
||||
features -= oldFeatures
|
||||
permissions -= oldPermissions
|
||||
featuresWritten = len(features) < 1
|
||||
permissionsWritten = len(permissions) < 1
|
||||
replace = {'-- %%INSERT_INIT_CLASSES%% --' : ':'.join(sorted(initClasses)),
|
||||
'-- %%BUNDLE_LOCAL_QT_LIBS%% --': '1',
|
||||
'-- %%USE_LOCAL_QT_LIBS%% --' : '1',
|
||||
'-- %%INSERT_LOCAL_LIBS%% --' : ':'.join(sorted(libs)),
|
||||
'-- %%INSERT_LOCAL_JARS%% --' : ':'.join(sorted(jars))}
|
||||
|
||||
with open(manifest) as inFile:
|
||||
with open(manifestTemp, 'w') as outFile:
|
||||
for line in inFile:
|
||||
for key in replace:
|
||||
line = line.replace(key, replace[key])
|
||||
|
||||
outFile.write(line)
|
||||
spaces = len(line)
|
||||
line = line.lstrip()
|
||||
spaces -= len(line)
|
||||
|
||||
if line.startswith('<uses-feature') and not featuresWritten:
|
||||
print('\nUpdating features\n')
|
||||
|
||||
for feature in features:
|
||||
print(' ' + feature)
|
||||
outFile.write(spaces * ' ' + '<uses-feature android:name="{}"/>\n'.format(feature))
|
||||
|
||||
featuresWritten = True
|
||||
|
||||
if line.startswith('<uses-permission') and not permissionsWritten:
|
||||
print('\nUpdating permissions\n')
|
||||
|
||||
for permission in permissions:
|
||||
print(' ' + permission)
|
||||
outFile.write(spaces * ' ' + '<uses-permission android:name="{}"/>\n'.format(permission))
|
||||
|
||||
permissionsWritten = True
|
||||
|
||||
os.remove(manifest)
|
||||
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)}
|
||||
confPath = os.path.dirname(self.qtConf)
|
||||
|
||||
if not os.path.exists(confPath):
|
||||
os.makedirs(confPath)
|
||||
|
||||
with open(self.qtConf, 'w') as qtconf:
|
||||
qtconf.write('[Paths]\n')
|
||||
|
||||
for path in paths:
|
||||
qtconf.write('{} = {}\n'.format(path, paths[path]))
|
||||
|
||||
@staticmethod
|
||||
def readChangeLog(changeLog, appName, version):
|
||||
if os.path.exists(changeLog):
|
||||
with open(changeLog) as f:
|
||||
for line in f:
|
||||
if not line.startswith('{0} {1}:'.format(appName, version)):
|
||||
continue
|
||||
|
||||
# Skip first line.
|
||||
f.readline()
|
||||
changeLogText = ''
|
||||
|
||||
for line_ in f:
|
||||
if re.match('{} \d+\.\d+\.\d+:'.format(appName), line):
|
||||
# Remove last line.
|
||||
i = changeLogText.rfind('\n')
|
||||
|
||||
if i >= 0:
|
||||
changeLogText = changeLogText[: i]
|
||||
|
||||
return changeLogText
|
||||
|
||||
changeLogText += line_
|
||||
|
||||
return ''
|
||||
|
||||
def createInstaller(self):
|
||||
if not os.path.exists(self.qtIFW):
|
||||
return False
|
||||
|
||||
# Read package config
|
||||
packageConf = configparser.ConfigParser()
|
||||
packageConf.optionxform=str
|
||||
packageConf.read(self.packageConfig, 'utf-8')
|
||||
|
||||
# Create layout
|
||||
componentName = 'com.{0}prj.{0}'.format(self.programName)
|
||||
packageDir = os.path.join(self.installerPackages, componentName)
|
||||
|
||||
if not os.path.exists(self.installerConfig):
|
||||
os.makedirs(self.installerConfig)
|
||||
|
||||
dataDir = os.path.join(packageDir, 'data')
|
||||
metaDir = os.path.join(packageDir, 'meta')
|
||||
|
||||
if not os.path.exists(dataDir):
|
||||
os.makedirs(dataDir)
|
||||
|
||||
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]
|
||||
licenseOutFile = os.path.basename(self.licenseFile)
|
||||
|
||||
if not '.' in licenseOutFile and \
|
||||
(self.targetSystem == 'windows' or \
|
||||
self.targetSystem == 'posix_windows'):
|
||||
licenseOutFile += '.txt'
|
||||
|
||||
self.copy(self.licenseFile, os.path.join(metaDir, licenseOutFile))
|
||||
self.copy(self.rootInstallDir, dataDir)
|
||||
|
||||
configXml = os.path.join(self.installerConfig, 'config.xml')
|
||||
appName = packageConf['Package']['appName'].strip()
|
||||
|
||||
with open(configXml, 'w') as config:
|
||||
config.write('<?xml version="1.0" encoding="UTF-8"?>\n')
|
||||
config.write('<Installer>\n')
|
||||
config.write(' <Name>{}</Name>\n'.format(appName))
|
||||
|
||||
if 'DAILY_BUILD' in os.environ:
|
||||
config.write(' <Version>0.0.0</Version>\n')
|
||||
else:
|
||||
config.write(' <Version>{}</Version>\n'.format(self.programVersion))
|
||||
|
||||
config.write(' <Title>{}</Title>\n'.format(packageConf['Package']['description'].strip()))
|
||||
config.write(' <Publisher>{}</Publisher>\n'.format(appName))
|
||||
config.write(' <ProductUrl>{}</ProductUrl>\n'.format(packageConf['Package']['url'].strip()))
|
||||
config.write(' <InstallerWindowIcon>{}</InstallerWindowIcon>\n'.format(iconName))
|
||||
config.write(' <InstallerApplicationIcon>{}</InstallerApplicationIcon>\n'.format(iconName))
|
||||
config.write(' <Logo>{}</Logo>\n'.format(iconName))
|
||||
config.write(' <TitleColor>{}</TitleColor>\n'.format(packageConf['Package']['titleColor'].strip()))
|
||||
config.write(' <RunProgram>{}</RunProgram>\n'.format(self.installerRunProgram))
|
||||
config.write(' <RunProgramDescription>{}</RunProgramDescription>\n'.format(packageConf['Package']['runMessage'].strip()))
|
||||
config.write(' <StartMenuDir>{}</StartMenuDir>\n'.format(appName))
|
||||
config.write(' <MaintenanceToolName>{}MaintenanceTool</MaintenanceToolName>\n'.format(appName))
|
||||
config.write(' <AllowNonAsciiCharacters>true</AllowNonAsciiCharacters>\n')
|
||||
config.write(' <TargetDir>{}</TargetDir>\n'.format(self.installerTargetDir))
|
||||
config.write('</Installer>\n')
|
||||
|
||||
self.copy(self.installerScript,
|
||||
os.path.join(metaDir, 'installscript.qs'))
|
||||
|
||||
with open(os.path.join(metaDir, 'package.xml'), 'w') as f:
|
||||
f.write('<?xml version="1.0"?>\n')
|
||||
f.write('<Package>\n')
|
||||
f.write(' <DisplayName>{}</DisplayName>\n'.format(appName))
|
||||
f.write(' <Description>{}</Description>\n'.format(packageConf['Package']['description'].strip()))
|
||||
|
||||
if 'DAILY_BUILD' in os.environ:
|
||||
f.write(' <Version>0.0.0</Version>\n')
|
||||
else:
|
||||
f.write(' <Version>{}</Version>\n'.format(self.programVersion))
|
||||
|
||||
f.write(' <ReleaseDate>{}</ReleaseDate>\n'.format(time.strftime('%Y-%m-%d')))
|
||||
f.write(' <Name>{}</Name>\n'.format(componentName))
|
||||
f.write(' <Licenses>\n')
|
||||
f.write(' <License name="{0}" file="{1}" />\n'.format(packageConf['Package']['licenseDescription'].strip(),
|
||||
licenseOutFile))
|
||||
f.write(' </Licenses>\n')
|
||||
f.write(' <Script>installscript.qs</Script>\n')
|
||||
f.write(' <UpdateText>\n')
|
||||
|
||||
if not 'DAILY_BUILD' in os.environ:
|
||||
f.write(self.readChangeLog(self.changeLog,
|
||||
appName,
|
||||
self.programVersion))
|
||||
|
||||
f.write(' </UpdateText>\n')
|
||||
f.write(' <Default>true</Default>\n')
|
||||
f.write(' <ForcedInstallation>true</ForcedInstallation>\n')
|
||||
f.write(' <Essential>false</Essential>\n')
|
||||
f.write('</Package>\n')
|
||||
|
||||
# Remove old file
|
||||
if not os.path.exists(self.pkgsDir):
|
||||
os.makedirs(self.pkgsDir)
|
||||
|
||||
if os.path.exists(self.outPackage):
|
||||
os.remove(self.outPackage)
|
||||
|
||||
params = []
|
||||
|
||||
if self.targetSystem == 'posix_windows':
|
||||
params = ['wine']
|
||||
|
||||
params += [self.qtIFW,
|
||||
'-c', configXml,
|
||||
'-p', self.installerPackages,
|
||||
self.outPackage]
|
||||
process = subprocess.Popen(params, # nosec
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
process.communicate()
|
||||
|
||||
return True
|
||||
|
||||
def copyAndroidTemplates(self):
|
||||
installPrefix = self.qmakeQuery(var='QT_INSTALL_PREFIX')
|
||||
sourcesPath = os.path.join(installPrefix, 'src')
|
||||
templates = [os.path.join(sourcesPath, '3rdparty/gradle'),
|
||||
os.path.join(sourcesPath, 'android/templates')]
|
||||
|
||||
for template in templates:
|
||||
self.copy(template, self.rootInstallDir, overwrite=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)
|
||||
|
||||
properties = os.path.join(self.rootInstallDir, 'gradle.properties')
|
||||
platform = self.androidPlatform.replace('android-', '')
|
||||
javaDir = os.path.join(sourcesPath, 'android','java')
|
||||
|
||||
with open(properties, 'w') as f:
|
||||
if 'sdkBuildToolsRevision' in deploymentSettings:
|
||||
f.write('androidBuildToolsVersion={}\n'.format(deploymentSettings['sdkBuildToolsRevision']))
|
||||
|
||||
f.write('androidCompileSdkVersion={}\n'.format(platform))
|
||||
f.write('buildDir=build\n')
|
||||
f.write('qt5AndroidDir={}\n'.format(javaDir))
|
||||
|
||||
def createRccBundle(self):
|
||||
rcc = os.path.join(os.path.dirname(self.qmake), 'rcc')
|
||||
assetsDir = os.path.abspath(os.path.join(self.assetsIntallDir, '..'))
|
||||
assetsFolder = os.path.relpath(self.assetsIntallDir, assetsDir)
|
||||
qrcFile = os.path.join(self.assetsIntallDir, assetsFolder + '.qrc')
|
||||
|
||||
params = [rcc,
|
||||
'--project',
|
||||
'-o', qrcFile]
|
||||
process = subprocess.Popen(params, # nosec
|
||||
cwd=self.assetsIntallDir,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
process.communicate()
|
||||
|
||||
params = [rcc,
|
||||
'--root=/{}'.format(assetsFolder),
|
||||
'--binary',
|
||||
'-o', self.assetsIntallDir + '.rcc',
|
||||
qrcFile]
|
||||
process = subprocess.Popen(params, # nosec
|
||||
cwd=assetsDir,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
process.communicate()
|
||||
|
||||
shutil.rmtree(self.assetsIntallDir, True)
|
259
ports/deploy/tools/utils.py
Normal file
259
ports/deploy/tools/utils.py
Normal file
|
@ -0,0 +1,259 @@
|
|||
#!/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 fnmatch
|
||||
import hashlib
|
||||
import multiprocessing
|
||||
import os
|
||||
import platform
|
||||
import shutil
|
||||
import subprocess # nosec
|
||||
import sys
|
||||
|
||||
class DeployToolsUtils:
|
||||
def __init__(self):
|
||||
self.make = ''
|
||||
|
||||
if os.name == 'posix' and sys.platform.startswith('darwin'):
|
||||
self.system = 'mac'
|
||||
elif os.name == 'nt' and sys.platform.startswith('win32'):
|
||||
self.system = 'windows'
|
||||
elif os.name == 'posix':
|
||||
self.system = 'posix'
|
||||
else:
|
||||
self.system = ''
|
||||
|
||||
self.arch = platform.architecture()[0]
|
||||
self.targetArch = self.arch
|
||||
self.targetSystem = self.system
|
||||
pathSep = ';' if self.system == 'windows' else ':'
|
||||
self.sysBinsPath = []
|
||||
|
||||
if 'PATH' in os.environ:
|
||||
self.sysBinsPath += \
|
||||
[path.strip() for path in os.environ['PATH'].split(pathSep)]
|
||||
|
||||
self.njobs = multiprocessing.cpu_count()
|
||||
|
||||
if self.njobs < 4:
|
||||
self.njobs = 4
|
||||
|
||||
def detectTargetArch(self, binary=''):
|
||||
if binary == '':
|
||||
binary = self.mainBinary
|
||||
|
||||
self.targetArch = platform.architecture(binary)[0]
|
||||
|
||||
def whereBin(self, binary):
|
||||
for path in self.sysBinsPath:
|
||||
path = os.path.join(path, binary)
|
||||
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
|
||||
return ''
|
||||
|
||||
def copy(self, src, dst='.', copyReals=False, overwrite=True):
|
||||
if not os.path.exists(src):
|
||||
return False
|
||||
|
||||
if os.path.isdir(src):
|
||||
if os.path.isfile(dst):
|
||||
return False
|
||||
|
||||
for root, dirs, files in os.walk(src):
|
||||
for f in files:
|
||||
fromF = os.path.join(root, f)
|
||||
toF = os.path.relpath(fromF, src)
|
||||
toF = os.path.join(dst, toF)
|
||||
toF = os.path.normpath(toF)
|
||||
self.copy(fromF, toF, copyReals, overwrite)
|
||||
|
||||
for d in dirs:
|
||||
fromD = os.path.join(root, d)
|
||||
toD = os.path.relpath(fromD, src)
|
||||
toD = os.path.join(dst, toD)
|
||||
|
||||
try:
|
||||
os.makedirs(os.path.normpath(toD))
|
||||
except:
|
||||
pass
|
||||
elif os.path.isfile(src):
|
||||
if os.path.isdir(dst):
|
||||
dst = os.path.realpath(dst)
|
||||
dst = os.path.join(dst, os.path.basename(src))
|
||||
|
||||
dirname = os.path.dirname(dst)
|
||||
|
||||
if not os.path.exists(dirname):
|
||||
try:
|
||||
os.makedirs(dirname)
|
||||
except:
|
||||
return False
|
||||
|
||||
if os.path.exists(dst):
|
||||
if not overwrite:
|
||||
return True
|
||||
|
||||
try:
|
||||
os.remove(dst)
|
||||
except:
|
||||
return False
|
||||
|
||||
if copyReals and os.path.islink(src):
|
||||
realpath = os.path.realpath(src)
|
||||
basename = os.path.basename(realpath)
|
||||
os.symlink(os.path.join('.', basename), dst)
|
||||
self.copy(realpath,
|
||||
os.path.join(dirname, basename),
|
||||
copyReals,
|
||||
overwrite)
|
||||
else:
|
||||
try:
|
||||
if self.system == 'windows':
|
||||
shutil.copy(src, dst)
|
||||
else:
|
||||
shutil.copy(src, dst, follow_symlinks=False)
|
||||
except:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def move(self, src, dst='.', moveReals=False):
|
||||
if not os.path.exists(src):
|
||||
return False
|
||||
|
||||
if os.path.isdir(src):
|
||||
if os.path.isfile(dst):
|
||||
return False
|
||||
|
||||
for root, dirs, files in os.walk(src):
|
||||
for f in files:
|
||||
fromF = os.path.join(root, f)
|
||||
toF = os.path.relpath(fromF, src)
|
||||
toF = os.path.join(dst, toF)
|
||||
toF = os.path.normpath(toF)
|
||||
self.move(fromF, toF, moveReals)
|
||||
|
||||
for d in dirs:
|
||||
fromD = os.path.join(root, d)
|
||||
toD = os.path.relpath(fromD, src)
|
||||
toD = os.path.join(dst, toD)
|
||||
|
||||
try:
|
||||
os.makedirs(os.path.normpath(toD))
|
||||
except:
|
||||
pass
|
||||
elif os.path.isfile(src):
|
||||
if os.path.isdir(dst):
|
||||
dst = os.path.realpath(dst)
|
||||
dst = os.path.join(dst, os.path.basename(src))
|
||||
|
||||
dirname = os.path.dirname(dst)
|
||||
|
||||
if not os.path.exists(dirname):
|
||||
try:
|
||||
os.makedirs(dirname)
|
||||
except:
|
||||
return False
|
||||
|
||||
if os.path.exists(dst):
|
||||
try:
|
||||
os.remove(dst)
|
||||
except:
|
||||
return False
|
||||
|
||||
if moveReals and os.path.islink(src):
|
||||
realpath = os.path.realpath(src)
|
||||
basename = os.path.basename(realpath)
|
||||
os.symlink(os.path.join('.', basename), dst)
|
||||
self.move(realpath, os.path.join(dirname, basename), moveReals)
|
||||
else:
|
||||
try:
|
||||
shutil.move(src, dst)
|
||||
except:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def detectMake(self):
|
||||
if 'MAKE_PATH' in os.environ:
|
||||
self.make = os.environ['MAKE_PATH']
|
||||
|
||||
return
|
||||
|
||||
makes = ['mingw32-make', 'make'] if self.system == 'windows' else ['make']
|
||||
ext = '.exe' if self.system == 'windows' else ''
|
||||
|
||||
for make in makes:
|
||||
makePath = self.whereBin(make + ext)
|
||||
|
||||
if makePath != '':
|
||||
self.make = makePath
|
||||
|
||||
break
|
||||
|
||||
def makeInstall(self, buildDir, installRoot=''):
|
||||
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)
|
||||
|
||||
process.communicate()
|
||||
os.chdir(previousDir)
|
||||
|
||||
return process.returncode
|
||||
|
||||
@staticmethod
|
||||
def sha256sum(fileName):
|
||||
sha = hashlib.sha256()
|
||||
|
||||
with open(fileName, 'rb') as f:
|
||||
while True:
|
||||
data = f.read(1024)
|
||||
|
||||
if not data:
|
||||
break
|
||||
|
||||
sha.update(data)
|
||||
|
||||
return sha.hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def detectMakeFiles(makePath):
|
||||
makeFiles = []
|
||||
|
||||
try:
|
||||
for f in os.listdir(makePath):
|
||||
path = os.path.join(makePath, f)
|
||||
|
||||
if os.path.isfile(path) and fnmatch.fnmatch(f.lower(), 'makefile*'):
|
||||
makeFiles += [path]
|
||||
except:
|
||||
pass
|
||||
|
||||
return makeFiles
|
Loading…
Reference in a new issue