1
0
mirror of https://gitlab.com/kelteseth/ScreenPlay.git synced 2024-09-18 08:22:33 +02:00
ScreenPlay/Tools/build.py
2022-07-22 13:21:30 +02:00

434 lines
18 KiB
Python
Executable File

#!/usr/bin/python3
import platform
import os
import subprocess
import platform
import shutil
import argparse
import time
import zipfile
from shutil import copytree
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
SCREENPLAY_VERSION = "0.15.0-RC1"
def zipdir(path, ziph):
# ziph is zipfile handle
for root, dirs, files in os.walk(path):
for file in files:
ziph.write(os.path.join(root, file),
os.path.relpath(os.path.join(root, file),
os.path.join(path, '..')))
# Based on https://gist.github.com/l2m2/0d3146c53c767841c6ba8c4edbeb4c2c
def get_vs_env_dict():
vcvars = "" # We support 2019 or 2022
# 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"
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.")
print(f"\n\nLoading MSVC env variables via {vcvars}\n\n")
cmd = [vcvars, '&&', 'set']
popen = subprocess.Popen(
cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = popen.communicate()
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)
def run_io_tasks_in_parallel(tasks):
with ThreadPoolExecutor() as executor:
running_tasks = [executor.submit(task) for task in tasks]
for running_task in running_tasks:
running_task.result()
def clean_build_dir(build_dir):
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)
def run(cmd, cwd=Path.cwd()):
result = subprocess.run(cmd, shell=True, cwd=cwd)
if result.returncode != 0:
raise RuntimeError(f"Failed to execute {cmd}")
def build(
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
):
# 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 = ""
qt_bin_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 if use_aqt else Path("C:/Qt")
qt_bin_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_bin_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"
elif(build_architecture == "x64"):
cmake_target_triplet = "x64-osx"
else:
print("MISSING BUILD ARCH: SET arm64 or x64")
exit(1)
qt_path = aqt_path if use_aqt else Path("~/Qt/")
qt_bin_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 if use_aqt else Path("~/Qt/")
qt_bin_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_bin_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_bin_path: {qt_bin_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}")
# Remove old build folder to before configuring to get rid of
# all cmake chaches
build_folder = root_path.joinpath(
f"build-{cmake_target_triplet}-{build_type}")
clean_build_dir(build_folder)
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} \
-DCMAKE_PREFIX_PATH={qt_bin_path} \
-DCMAKE_BUILD_TYPE={build_type} \
-DVCPKG_TARGET_TRIPLET={cmake_target_triplet} \
-DCMAKE_TOOLCHAIN_FILE={cmake_toolchain_file} \
-DSCREENPLAY_STEAM={build_steam} \
-DSCREENPLAY_TESTS={build_tests} \
-DSCREENPLAY_RELEASE={build_release} \
-DSCREENPLAY_INSTALLER={create_installer} \
-DSCREENPLAY_IFW_ROOT:STRING={ifw_root_path} \
-G "CodeBlocks - Ninja" \
-B.'
# 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_bin_path,
app="ScreenPlay",
executable_file_ending=executable_file_ending), cwd=bin_dir)
run(deploy_command.format(
type=build_type,
prefix_path=qt_bin_path,
app="ScreenPlayWidget",
executable_file_ending=executable_file_ending), cwd=bin_dir)
run(deploy_command.format(
type=build_type,
prefix_path=qt_bin_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_bin_path lib folder into bin_dir
qt_lib_path = qt_bin_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_bin_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_bin_path plugins subfolder into bin_dir
qt_plugins_path = qt_bin_path
for folder in qt_bin_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_bin_path translation files into bin_dir translation folder
qt_translations_path = qt_bin_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_bin_path resources folder into bin_dir folder
qt_resources_path = qt_bin_path
for file in qt_bin_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)
# 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"))
# 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))
if __name__ == "__main__":
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\nLinux & 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
)