2022-01-04 10:12:03 +01:00
#!/usr/bin/python3
import platform
2020-10-29 18:19:22 +01:00
import os
import subprocess
2022-07-08 12:33:26 +02:00
import platform
2020-10-29 18:19:22 +01:00
import shutil
import argparse
2022-01-04 10:12:03 +01:00
import time
2022-07-09 13:05:42 +02:00
import zipfile
2022-07-15 13:05:47 +02:00
from shutil import copytree
2022-01-04 10:12:03 +01:00
from pathlib import Path
from concurrent . futures import ThreadPoolExecutor
2022-07-09 13:05:42 +02:00
from datetime import datetime
SCREENPLAY_VERSION = " 0.15.0-RC1 "
2022-07-15 12:43:57 +02:00
2022-07-09 13:05:42 +02:00
def zipdir ( path , ziph ) :
# ziph is zipfile handle
for root , dirs , files in os . walk ( path ) :
for file in files :
2022-07-15 12:43:57 +02:00
ziph . write ( os . path . join ( root , file ) ,
os . path . relpath ( os . path . join ( root , file ) ,
2022-07-09 13:05:42 +02:00
os . path . join ( path , ' .. ' ) ) )
2020-10-29 19:17:45 +01:00
2022-01-04 10:12:03 +01:00
# Based on https://gist.github.com/l2m2/0d3146c53c767841c6ba8c4edbeb4c2c
def get_vs_env_dict ( ) :
2022-07-15 12:43:57 +02:00
vcvars = " " # We support 2019 or 2022
2022-04-04 18:06:43 +02:00
2022-07-15 12:43:57 +02:00
# Hardcoded VS path
# check if vcvars64.bat is available.
msvc_2019_path = " C: \\ Program Files (x86) \\ Microsoft Visual Studio \\ 2019 \\ Community \\ VC \\ Auxiliary \\ Build \\ vcvars64.bat "
msvc_2022_path = " C: \\ Program Files \\ Microsoft Visual Studio \\ 2022 \\ Community \\ VC \\ Auxiliary \\ Build \\ vcvars64.bat "
2022-01-04 10:12:03 +01:00
2022-07-15 12:43:57 +02:00
if Path ( msvc_2019_path ) . exists ( ) :
vcvars = msvc_2019_path
# Prefer newer MSVC and override if exists
if Path ( msvc_2022_path ) . exists ( ) :
vcvars = msvc_2022_path
if not vcvars :
raise RuntimeError (
" No Visual Studio installation found, only 2019 and 2022 are supported. " )
2022-01-04 10:12:03 +01:00
2022-07-15 12:43:57 +02:00
print ( f " \n \n Loading MSVC env variables via { vcvars } \n \n " )
2022-01-04 10:12:03 +01:00
2022-07-15 12:43:57 +02:00
cmd = [ vcvars , ' && ' , ' set ' ]
popen = subprocess . Popen (
cmd , shell = True , stdout = subprocess . PIPE , stderr = subprocess . PIPE )
stdout , stderr = popen . communicate ( )
2022-01-04 10:12:03 +01:00
2022-07-15 12:43:57 +02:00
if popen . wait ( ) != 0 :
raise ValueError ( stderr . decode ( " mbcs " ) )
output = stdout . decode ( " mbcs " ) . split ( " \r \n " )
return dict ( ( e [ 0 ] . upper ( ) , e [ 1 ] ) for e in [ p . rstrip ( ) . split ( " = " , 1 ) for p in output ] if len ( e ) == 2 )
2020-10-29 18:19:22 +01:00
2022-07-09 13:05:42 +02:00
2022-01-04 10:12:03 +01:00
def run_io_tasks_in_parallel ( tasks ) :
2022-07-15 12:43:57 +02:00
with ThreadPoolExecutor ( ) as executor :
running_tasks = [ executor . submit ( task ) for task in tasks ]
for running_task in running_tasks :
running_task . result ( )
2022-01-04 10:12:03 +01:00
2022-07-09 13:05:42 +02:00
2022-01-04 10:12:03 +01:00
def clean_build_dir ( build_dir ) :
2022-07-15 12:43:57 +02:00
if isinstance ( build_dir , str ) :
build_dir = Path ( build_dir )
if build_dir . exists ( ) :
print ( f " Remove previous build folder: { build_dir } " )
# ignore_errors removes also not empty folders...
shutil . rmtree ( build_dir , ignore_errors = True )
build_dir . mkdir ( parents = True , exist_ok = True )
2022-01-04 10:12:03 +01:00
2022-07-09 13:05:42 +02:00
def run ( cmd , cwd = Path . cwd ( ) ) :
2022-07-15 12:43:57 +02:00
result = subprocess . run ( cmd , shell = True , cwd = cwd )
if result . returncode != 0 :
raise RuntimeError ( f " Failed to execute { cmd } " )
2022-01-04 10:12:03 +01:00
2022-07-09 13:05:42 +02:00
def build (
2022-07-15 12:43:57 +02:00
qt_version : str ,
qt_ifw_version : str ,
build_steam : str ,
build_tests : str ,
build_release : str ,
create_installer : str ,
build_type : str ,
sign_build : str ,
use_aqt : bool ,
build_architecture : str
2022-07-09 13:05:42 +02:00
) :
2022-07-08 12:49:53 +02:00
2022-07-15 12:43:57 +02:00
# Make sure the script is always started from the same folder
root_path = Path . cwd ( )
if root_path . name == " Tools " :
root_path = root_path . parent
print ( f " Change root directory to: { root_path } " )
os . chdir ( root_path )
aqt_path = " "
if use_aqt :
aqt_path = Path ( f " { root_path } /../aqt/ " ) . resolve ( )
if not Path ( aqt_path ) . exists ( ) :
print (
f " aqt path does not exist at { aqt_path } . Please make sure you have installed aqt. " )
exit ( 2 )
deploy_command : str = " "
executable_file_ending : str = " "
qt_path : str = " "
aqt_install_qt_packages : str = " "
aqt_install_tool_packages : str = " "
cmake_target_triplet : str = " "
file_endings = [ " .ninja_deps " , " .ninja " , " .ninja_log " , " .lib " , " .a " , " .exp " ,
" .manifest " , " .cmake " , " .cbp " , " CMakeCache.txt " ]
if platform . system ( ) == " Windows " :
cmake_target_triplet = " x64-windows "
windows_msvc = " msvc2019_64 "
executable_file_ending = " .exe "
qt_path = aqt_path . joinpath ( qt_version ) . joinpath (
windows_msvc ) if use_aqt else Path ( f " C:/Qt/ { qt_version } / { windows_msvc } " )
vs_env_dict = get_vs_env_dict ( )
vs_env_dict [ " PATH " ] = vs_env_dict [ " PATH " ] + \
" ; " + str ( qt_path ) + " \\ bin "
os . environ . update ( vs_env_dict )
deploy_command = " windeployqt.exe -- {type} --qmldir ../../ {app} /qml {app} {executable_file_ending} "
aqt_install_qt_packages = f " windows desktop { qt_version } win64_msvc2019_64 -m all "
aqt_install_tool_packages = " windows desktop tools_ifw "
elif platform . system ( ) == " Darwin " :
if ( build_architecture == " arm64 " ) :
cmake_target_triplet = " arm64-osx "
else :
cmake_target_triplet = " x64-osx "
qt_path = aqt_path . joinpath (
f " { qt_version } /macos " ) if use_aqt else Path ( f " ~/Qt/ { qt_version } /macos " )
deploy_command = " {prefix_path} /bin/macdeployqt {app} .app -qmldir=../../ {app} /qml -executable= {app} .app/Contents/MacOS/ {app} "
aqt_install_qt_packages = f " mac desktop { qt_version } clang_64 -m all "
aqt_install_tool_packages = " mac desktop tools_ifw "
elif platform . system ( ) == " Linux " :
cmake_target_triplet = " x64-linux "
qt_path = aqt_path . joinpath (
f " { qt_version } /gcc_64 " ) if use_aqt else Path ( f " ~/Qt/ { qt_version } /gcc_64 " )
aqt_install_qt_packages = f " linux desktop { qt_version } gcc_64 -m all "
aqt_install_tool_packages = " linux desktop tools_ifw "
if shutil . which ( " cqtdeployer " ) :
deploy_command = " cqtdeployer -qmlDir ../../ {app} /qml -bin {app} "
else :
print ( " cqtdeployer not available, build may be incomplete and incompatible with some distro (typically Ubuntu) " )
home_path = str ( Path . home ( ) )
qt_path = aqt_path . joinpath (
f " { qt_version } /gcc_64 " ) if use_aqt else Path ( f " { home_path } /Qt/ { qt_version } /gcc_64 " )
else :
raise NotImplementedError (
" Unsupported platform, ScreenPlay only supports Windows, macOS and Linux. " )
# Default to QtMaintainance installation.
if use_aqt :
print ( f " qt_path: { qt_path } . " )
if not Path ( aqt_path ) . exists ( ) :
print (
f " aqt path does not exist at { aqt_path } . Installing now into... " )
run ( f " aqt install-qt -O ../aqt { aqt_install_qt_packages } " , cwd = root_path )
run (
f " aqt install-tool -O ../aqt { aqt_install_tool_packages } " , cwd = root_path )
# Prepare
cmake_toolchain_file = f " ' { root_path } /../ScreenPlay-vcpkg/scripts/buildsystems/vcpkg.cmake ' "
ifw_root_path = f " { qt_path } \\ Tools \\ QtInstallerFramework \\ { qt_ifw_version } "
print ( f " cmake_toolchain_file: { cmake_toolchain_file } " )
print (
f " Starting build with type { build_type } . Qt Version: { qt_version } . Root path: { root_path } " )
CMAKE_OSX_ARCHITECTURES : str = " "
# The entire parameter should be optional. We need this to explicity build
# x84 and arm version seperat, only because we cannot compile two at once with vcpkg
if build_architecture :
CMAKE_OSX_ARCHITECTURES = f " -DCMAKE_OSX_ARCHITECTURES= { build_architecture } "
cmake_configure_command = f ' cmake ../ \
{ CMAKE_OSX_ARCHITECTURES } \
2022-05-06 17:23:14 +02:00
- DCMAKE_PREFIX_PATH = { qt_path } \
2022-07-09 13:05:42 +02:00
- DCMAKE_BUILD_TYPE = { build_type } \
2022-05-06 17:23:14 +02:00
- DVCPKG_TARGET_TRIPLET = { cmake_target_triplet } \
- DCMAKE_TOOLCHAIN_FILE = { cmake_toolchain_file } \
2022-05-12 14:36:18 +02:00
- DSCREENPLAY_STEAM = { build_steam } \
2022-05-06 17:23:14 +02:00
- DSCREENPLAY_TESTS = { build_tests } \
2022-05-12 14:36:18 +02:00
- DSCREENPLAY_RELEASE = { build_release } \
2022-05-20 15:52:32 +02:00
- DSCREENPLAY_INSTALLER = { create_installer } \
2022-05-06 17:23:14 +02:00
- DSCREENPLAY_IFW_ROOT : STRING = { ifw_root_path } \
2022-01-04 10:12:03 +01:00
- G " CodeBlocks - Ninja " \
2022-05-06 17:23:14 +02:00
- B . '
2022-01-04 10:12:03 +01:00
2022-07-15 12:43:57 +02:00
build_folder = root_path . joinpath (
f " build- { cmake_target_triplet } - { build_type } " )
clean_build_dir ( build_folder )
# Build
start_time = time . time ( )
print ( cmake_configure_command )
run ( cmake_configure_command , cwd = build_folder )
run ( " cmake --build . --target all " , cwd = build_folder )
bin_dir = build_folder . joinpath ( " bin " )
# Deploy dependencies
if deploy_command : # Only deploy if we have the dependencies
print ( " Executing deploy commands... " )
run ( deploy_command . format (
type = build_type ,
prefix_path = qt_path ,
app = " ScreenPlay " ,
executable_file_ending = executable_file_ending ) , cwd = bin_dir )
run ( deploy_command . format (
type = build_type ,
prefix_path = qt_path ,
app = " ScreenPlayWidget " ,
executable_file_ending = executable_file_ending ) , cwd = bin_dir )
run ( deploy_command . format (
type = build_type ,
prefix_path = qt_path ,
app = " ScreenPlayWallpaper " ,
executable_file_ending = executable_file_ending ) , cwd = bin_dir )
else :
# just copy the folders and be done with it
if platform . system ( ) == " Linux " :
# Copy all .so files from the qt_path lib folder into bin_dir
qt_lib_path = qt_path
for file in qt_lib_path . joinpath ( " lib " ) . glob ( " *.so " ) :
shutil . copy ( str ( file ) , str ( bin_dir ) )
# Copy qt_qml_path folder content into bin_dir
qt_qml_path = qt_path
for folder in qt_qml_path . joinpath ( " qml " ) . iterdir ( ) :
if not folder . is_file ( ) :
shutil . copytree ( str ( folder ) , str (
bin_dir . joinpath ( folder . name ) ) )
print ( " Copied %s " % folder )
# Copy all plugin folder from qt_path plugins subfolder into bin_dir
qt_plugins_path = qt_path
for folder in qt_path . joinpath ( " plugins " ) . iterdir ( ) :
if not folder . is_file ( ) :
shutil . copytree ( str ( folder ) , str (
bin_dir . joinpath ( folder . name ) ) )
print ( " Copied %s " % folder )
# Copy all folder from qt_path translation files into bin_dir translation folder
qt_translations_path = qt_path
for folder in qt_translations_path . joinpath ( " translations " ) . iterdir ( ) :
if not folder . is_file ( ) :
shutil . copytree ( str ( folder ) , str (
bin_dir . joinpath ( " translations " ) . joinpath ( folder . name ) ) )
print ( " Copied %s " % folder )
# Copy all filesfrom qt_path resources folder into bin_dir folder
qt_resources_path = qt_path
for file in qt_path . joinpath ( " resources " ) . glob ( " * " ) :
shutil . copy ( str ( file ) , str ( bin_dir ) )
print ( " Copied %s " % file )
else :
print ( " Not executing deploy commands due to missing dependencies. " )
# Post-build
if platform . system ( ) == " Darwin " and sign_build == " ON " :
run ( " codesign --deep -f -s \" Developer ID Application: Elias Steurer (V887LHYKRH) \" --timestamp --options \" runtime \" -f --entitlements \" ../../ScreenPlay/entitlements.plist \" --deep \" ScreenPlay.app/ \" " , cwd = bin_dir )
run ( " codesign --deep -f -s \" Developer ID Application: Elias Steurer (V887LHYKRH) \" --timestamp --options \" runtime \" -f --deep \" ScreenPlayWallpaper.app/ \" " , cwd = bin_dir )
run ( " codesign --deep -f -s \" Developer ID Application: Elias Steurer (V887LHYKRH) \" --timestamp --options \" runtime \" -f --deep \" ScreenPlayWidget.app/ \" " , cwd = bin_dir )
run ( " codesign --verify --verbose=4 \" ScreenPlay.app/ \" " , cwd = bin_dir )
run ( " codesign --verify --verbose=4 \" ScreenPlayWallpaper.app/ \" " , cwd = bin_dir )
run ( " codesign --verify --verbose=4 \" ScreenPlayWidget.app/ \" " , cwd = bin_dir )
run ( " xcnotary notarize ScreenPlay.app -d kelteseth@gmail.com -k ScreenPlay " , cwd = bin_dir ) ,
run ( " xcnotary notarize ScreenPlayWallpaper.app -d kelteseth@gmail.com -k ScreenPlay " , cwd = bin_dir ) ,
run ( " xcnotary notarize ScreenPlayWidget.app -d kelteseth@gmail.com -k ScreenPlay " , cwd = bin_dir )
run ( " spctl --assess --verbose \" ScreenPlay.app/ \" " , cwd = bin_dir )
run ( " spctl --assess --verbose \" ScreenPlayWallpaper.app/ \" " , cwd = bin_dir )
run ( " spctl --assess --verbose \" ScreenPlayWidget.app/ \" " , cwd = bin_dir )
2022-07-15 13:05:47 +02:00
# Copy qml dir into all .app/Contents/MacOS/
if platform . system ( ) == " Darwin " :
copytree ( Path . joinpath ( bin_dir , " qml " ) , Path . joinpath ( bin_dir , " ScreenPlay.app/Contents/MacOS/qml " ) )
copytree ( Path . joinpath ( bin_dir , " qml " ) , Path . joinpath ( bin_dir , " ScreenPlayWallpaper.app/Contents/MacOS/qml " ) )
copytree ( Path . joinpath ( bin_dir , " qml " ) , Path . joinpath ( bin_dir , " ScreenPlayWidget.app/Contents/MacOS/qml " ) )
2022-07-15 12:43:57 +02:00
# Some dlls like openssl do no longer get copied automatically.
# Lets just copy all of them into bin.
if platform . system ( ) == " Windows " :
vcpkg_bin_path = Path (
f " { root_path } /../ScreenPlay-vcpkg/installed/x64-windows/bin " ) . resolve ( )
print ( vcpkg_bin_path )
for file in vcpkg_bin_path . iterdir ( ) :
if file . suffix == " .dll " and file . is_file ( ) :
print ( file , bin_dir )
shutil . copy2 ( file , bin_dir )
if not platform . system ( ) == " Darwin " :
for file_ending in file_endings :
for file in bin_dir . rglob ( " * " + file_ending ) :
if file . is_file ( ) :
print ( " Remove: %s " % file . resolve ( ) )
file . unlink ( )
if create_installer == " ON " :
os . chdir ( build_folder )
print ( " Running cpack at: " , os . getcwd ( ) )
run ( " cpack " , cwd = build_folder )
# We also need to sign the installer in osx:
if platform . system ( ) == " Darwin " and sign_build == " ON " :
run ( " codesign --deep -f -s \" Developer ID Application: Elias Steurer (V887LHYKRH) \" --timestamp --options \" runtime \" -f --deep \" ScreenPlay-Installer.dmg/ScreenPlay-Installer.app/Contents/MacOS/ScreenPlay-Installer \" " , cwd = build_folder )
run ( " codesign --verify --verbose=4 \" ScreenPlay-Installer.dmg/ScreenPlay-Installer.app/Contents/MacOS/ScreenPlay-Installer \" " , cwd = build_folder )
run ( " xcnotary notarize ScreenPlay-Installer.dmg/ScreenPlay-Installer.app -d kelteseth@gmail.com -k ScreenPlay " , cwd = build_folder )
run ( " spctl --assess --verbose \" ScreenPlay-Installer.dmg/ScreenPlay-Installer.app/ \" " , cwd = build_folder )
run ( " codesign --deep -f -s \" Developer ID Application: Elias Steurer (V887LHYKRH) \" --timestamp --options \" runtime \" -f --deep \" ScreenPlay-Installer.dmg/ \" " , cwd = build_folder )
run ( " codesign --verify --verbose=4 \" ScreenPlay-Installer.dmg/ \" " ,
cwd = build_folder )
run ( " xcnotary notarize ScreenPlay-Installer.dmg -d kelteseth@gmail.com -k ScreenPlay " , cwd = build_folder )
run ( " spctl --assess --verbose \" ScreenPlay-Installer.dmg/ \" " ,
cwd = build_folder )
# Create a zip file for scoop
if platform . system ( ) == " Windows " :
zipName = f " ScreenPlay- { SCREENPLAY_VERSION } - { cmake_target_triplet } - { build_type } .zip "
print ( f " Creating scoop zip file: \n { bin_dir } \n { zipName } " )
os . chdir ( build_folder )
with zipfile . ZipFile ( zipName , ' w ' , zipfile . ZIP_DEFLATED ) as zipf :
zipdir ( bin_dir , zipf )
print ( " Time taken: {} s " . format ( time . time ( ) - start_time ) )
2022-07-09 13:05:42 +02:00
if __name__ == " __main__ " :
2022-07-15 12:43:57 +02:00
parser = argparse . ArgumentParser (
description = ' Build and Package ScreenPlay ' )
parser . add_argument ( ' -qt-version ' , action = " store " , dest = " qt_version_overwrite " ,
help = " Overwrites the default Qt version " )
parser . add_argument ( ' -qt-installer-version ' , action = " store " , dest = " qt_installer_version_overwrite " ,
help = " Overwrites the default Qt installer framework version " )
parser . add_argument ( ' -type ' , action = " store " , dest = " build_type " ,
help = " Build type. This is either debug or release. " )
parser . add_argument ( ' -use-aqt ' , action = " store_true " , dest = " use_aqt " ,
help = " Absolute qt path. If not set the default path is used \ Windows: C: \ Qt \n Linux & macOS:~/Qt/. " )
parser . add_argument ( ' -sign ' , action = " store_true " , dest = " sign_build " ,
help = " Enable if you want to sign the apps. This is macOS only for now. " )
parser . add_argument ( ' -steam ' , action = " store_true " , dest = " build_steam " ,
help = " Enable if you want to build the Steam workshop plugin. " )
parser . add_argument ( ' -tests ' , action = " store_true " , dest = " build_tests " ,
help = " Build tests. " )
parser . add_argument ( ' -installer ' , action = " store_true " , dest = " create_installer " ,
help = " Create a installer. " )
parser . add_argument ( ' -release ' , action = " store_true " , dest = " build_release " ,
help = " Create a release version of ScreenPlay for sharing with the world. This is not about debug/release build, but the c++ define SCREENPLAY_RELEASE. " )
parser . add_argument ( ' -architecture ' , action = " store " , dest = " build_architecture " ,
help = " Sets the build architecture. Used to build x86 and ARM osx versions. Currently only works with x86_64 and arm64 " )
args = parser . parse_args ( )
qt_version = " 6.3.0 "
qt_ifw_version = " 4.4 " # Not yet used.
qt_version_overwrite = " "
use_aqt = False
if args . qt_version_overwrite :
qt_version = args . qt_version_overwrite
print ( " Using Qt version {qt_version} " )
if args . qt_installer_version_overwrite :
qt_ifw_version = args . qt_installer_version_overwrite
print ( " Using Qt installer framework version {qt_ifw_version} " )
if not args . build_type :
print ( " Build type argument is missing (release, debug). E.g: python build.py -type release -steam " )
print (
f " Defaulting to Qt version: { qt_version } . This can be overwritten via -qt-version 6.5.0 " )
exit ( 1 )
build_type = args . build_type
build_steam = " OFF "
if args . build_steam :
build_steam = " ON "
build_tests = " OFF "
if args . build_tests :
build_tests = " ON "
build_release = " OFF "
if args . build_release :
build_release = " ON "
create_installer = " OFF "
if args . create_installer :
create_installer = " ON "
sign_build = " OFF "
if args . sign_build :
sign_build = " ON "
build (
qt_version ,
qt_ifw_version ,
build_steam ,
build_tests ,
build_release ,
create_installer ,
build_type ,
sign_build ,
args . use_aqt ,
args . build_architecture
)