Added CI and deploy scripts.

This commit is contained in:
Gonzalo Exequiel Pedone 2020-06-05 19:09:17 -03:00
parent eb25244ca5
commit c9811d355d
No known key found for this signature in database
GPG key ID: B8B09E63E9B85BAF
38 changed files with 6058 additions and 0 deletions

112
.travis.yml Normal file
View 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
View 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

View 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
View 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

View 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
View 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

View 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

View 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

View 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
)
)

View 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
View 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
View 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
View 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

View 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);
}

View 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
View 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
View 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

View 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
View 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
View 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()

View 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()

View 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()

View 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()

View file

@ -0,0 +1,2 @@
C:/Windows/System32/.*
C:/Program Files/.*

View file

@ -0,0 +1,2 @@
/usr/lib/.*
/System/Library/Frameworks/.*

View 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

View 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

View file

@ -0,0 +1,13 @@
function Component()
{
}
Component.prototype.beginInstallation = function()
{
component.beginInstallation();
}
Component.prototype.createOperations = function()
{
component.createOperations();
}

View 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");
}

View 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");
}

View 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

View 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)

View 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)

View 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'

View 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

View 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
View 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
View 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