684 lines
28 KiB
Python
684 lines
28 KiB
Python
#!/usr/bin/env python
|
|
|
|
# SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me>
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
# This Python script is meant to prepare a Krita package folder to be zipped or
|
|
# to be a base for the installer.
|
|
|
|
import argparse
|
|
import glob
|
|
import itertools
|
|
import os
|
|
import re
|
|
import pathlib
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from components import MergeFolders, CommonUtils
|
|
|
|
# Subroutines
|
|
|
|
def choice(prompt="Is this ok?"):
|
|
c = input(f"{prompt} [y/n] ")
|
|
if c == "y":
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def find_on_path(variable, executable):
|
|
exeString = shutil.which(executable)
|
|
if exeString:
|
|
os.environ[variable] = exeString
|
|
|
|
|
|
def prompt_for_file(prompt):
|
|
user_input = input(f"{prompt} ")
|
|
if not len(user_input):
|
|
return None
|
|
result = os.path.isfile(user_input)
|
|
if result is None:
|
|
print("Input does not point to valid dir!")
|
|
return prompt_for_file(prompt)
|
|
else:
|
|
return os.path.realpath(user_input)
|
|
|
|
|
|
def prompt_for_dir(prompt):
|
|
user_input = input(f"{prompt} ")
|
|
if not len(user_input):
|
|
return None
|
|
result = os.path.isdir(user_input)
|
|
if result is None:
|
|
print("Input does not point to valid dir!")
|
|
return prompt_for_dir(prompt)
|
|
else:
|
|
return os.path.realpath(user_input)
|
|
|
|
# command-line args parsing
|
|
parser = argparse.ArgumentParser()
|
|
|
|
basic_options = parser.add_argument_group("Basic options")
|
|
basic_options.add_argument("--no-interactive", action='store_true',
|
|
help="Run without interactive prompts. When not specified, the script will prompt for some of the parameters.")
|
|
basic_options.add_argument(
|
|
"--package-name", action='store', help="Specify the package name", required=True)
|
|
|
|
path_options = parser.add_argument_group("Path options")
|
|
path_options.add_argument("--src-dir", action='store',
|
|
help="Specify Krita source dir. If unspecified, this will be determined from the script location")
|
|
path_options.add_argument("--deps-install-dir", action='store',
|
|
help="Specify deps install dir")
|
|
path_options.add_argument("--krita-install-dir", action='store',
|
|
help="Specify Krita install dir")
|
|
|
|
special_options = parser.add_argument_group("Special options")
|
|
special_options.add_argument("--pre-zip-hook", action='store',
|
|
help="Specify a script to be called before packaging the zip archive, can be used to sign the binaries")
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Check environment config
|
|
|
|
useVerbosePackagingLog = CommonUtils.useVerbosePackagingLog()
|
|
logger = CommonUtils.createLogger(f"{os.path.basename(__file__)}.log")
|
|
|
|
def run_subprocess_checked(argList):
|
|
logger.info(f"Run: {' '.join(argList)}")
|
|
subprocess.run(argList, check=True)
|
|
|
|
if os.environ.get("SEVENZIP_EXE") is None:
|
|
find_on_path("SEVENZIP_EXE", "7z.exe")
|
|
if os.environ.get("SEVENZIP_EXE") is None:
|
|
find_on_path("SEVENZIP_EXE", "7za.exe")
|
|
if os.environ.get("SEVENZIP_EXE") is None:
|
|
os.environ["SEVENZIP_EXE"] = f"{os.environ['ProgramFiles']}\\7-Zip\\7z.exe"
|
|
if not os.path.isfile(os.environ["SEVENZIP_EXE"]):
|
|
os.environ["SEVENZIP_EXE"] = "{}\\7-Zip\\7z.exe".format(
|
|
os.environ["ProgramFiles(x86)"])
|
|
if not os.path.isfile(os.environ["SEVENZIP_EXE"]):
|
|
logger.error("7-Zip not found!")
|
|
sys.exit(102)
|
|
logger.info(f"7-Zip: {os.environ['SEVENZIP_EXE']}")
|
|
|
|
if not os.environ.get("MINGW_BIN_DIR"):
|
|
find_on_path("MINGW_BIN_DIR_MAKE_EXE", "mingw32-make.exe")
|
|
if not os.environ.get('MINGW_BIN_DIR_MAKE_EXE'):
|
|
if not args.no_interactive:
|
|
os.environ['MINGW_BIN_DIR_MAKE_EXE'] = prompt_for_file("Provide path to mingw32-make.exe of mingw-w64")
|
|
if not os.environ.get('MINGW_BIN_DIR_MAKE_EXE'):
|
|
logger.error("mingw-w64 not found!")
|
|
sys.exit(102)
|
|
os.environ['MINGW_BIN_DIR'] = os.path.dirname(os.environ['MINGW_BIN_DIR_MAKE_EXE'])
|
|
else:
|
|
os.environ['MINGW_BIN_DIR'] = os.path.dirname(os.environ['MINGW_BIN_DIR_MAKE_EXE'])
|
|
logger.info(f"Found mingw on PATH: {os.environ['MINGW_BIN_DIR_MAKE_EXE']}")
|
|
if not args.no_interactive:
|
|
status = choice("Is this correct?")
|
|
if not status:
|
|
os.environ['MINGW_BIN_DIR_MAKE_EXE'] = prompt_for_dir("Provide path to mingw32-make.exe of mingw-w64")
|
|
if not os.environ['MINGW_BIN_DIR_MAKE_EXE']:
|
|
logger.error("mingw-w64 not found!")
|
|
sys.exit(102)
|
|
os.environ['MINGW_BIN_DIR'] = os.path.dirname(os.environ['MINGW_BIN_DIR_MAKE_EXE'])
|
|
logger.info(f"mingw-w64: {os.environ['MINGW_BIN_DIR']}")
|
|
|
|
# Windows SDK is needed for windeployqt to get d3dcompiler_xx.dll
|
|
# If we don't define this variable, windeployqt will fetch the library
|
|
# from %WINDIR%\system32, which is not supposed to be redistributable
|
|
if os.environ.get("WindowsSdkDir") is None and os.environ.get("ProgramFiles(x86)") is not None:
|
|
os.environ["WindowsSdkDir"] = "{}\\Windows Kits\\10".format(
|
|
os.environ["ProgramFiles(x86)"])
|
|
|
|
KRITA_SRC_DIR = None
|
|
|
|
if args.src_dir is not None:
|
|
KRITA_SRC_DIR = args.src_dir
|
|
|
|
if KRITA_SRC_DIR is None:
|
|
_temp = sys.argv[0]
|
|
if os.path.dirname(_temp).endswith("\\packaging\\windows"):
|
|
_base = pathlib.PurePath(_temp)
|
|
if os.path.isfile(f"{_base.parents[2]}\\CMakeLists.txt"):
|
|
if os.path.isfile(f"{_base.parents[2]}\\libs\\version\\kritaversion.h.cmake"):
|
|
KRITA_SRC_DIR = os.path.realpath(_base.parents[2])
|
|
logger.info("Script is running inside Krita source dir")
|
|
|
|
if KRITA_SRC_DIR is None:
|
|
if args.no_interactive:
|
|
KRITA_SRC_DIR = prompt_for_dir("Provide path of Krita src dir")
|
|
if KRITA_SRC_DIR is None:
|
|
logger.error("Krita src dir not found!")
|
|
sys.exit(102)
|
|
logger.info(f"Krita src: {KRITA_SRC_DIR}")
|
|
|
|
DEPS_INSTALL_DIR = None
|
|
|
|
if args.deps_install_dir is not None:
|
|
DEPS_INSTALL_DIR = args.deps_install_dir
|
|
if DEPS_INSTALL_DIR is None:
|
|
DEPS_INSTALL_DIR = f"{os.getcwd()}\\_install"
|
|
logger.info(f"Using default deps install dir: {DEPS_INSTALL_DIR}")
|
|
if not args.no_interactive:
|
|
status = choice()
|
|
if not status:
|
|
DEPS_INSTALL_DIR = prompt_for_dir(
|
|
"Provide path of deps install dir")
|
|
if DEPS_INSTALL_DIR is None:
|
|
logger.error("Deps install dir not set!")
|
|
sys.exit(102)
|
|
logger.info(f"Deps install dir: {DEPS_INSTALL_DIR}")
|
|
|
|
KRITA_INSTALL_DIR = None
|
|
|
|
if args.krita_install_dir is not None:
|
|
KRITA_INSTALL_DIR = args.krita_install_dir
|
|
if KRITA_INSTALL_DIR is None:
|
|
KRITA_INSTALL_DIR = f"{os.getcwd()}\\_install"
|
|
logger.info(f"Using default Krita install dir: {KRITA_INSTALL_DIR}")
|
|
if not args.no_interactive:
|
|
status = choice()
|
|
if not status:
|
|
KRITA_INSTALL_DIR = prompt_for_dir(
|
|
"Provide path of Krita install dir")
|
|
if KRITA_INSTALL_DIR is None:
|
|
logger.error("Krita install dir not set!")
|
|
sys.exit(102)
|
|
logger.info(f"Krita install dir: {KRITA_INSTALL_DIR}")
|
|
|
|
# Simple checking
|
|
if not os.path.isdir(DEPS_INSTALL_DIR):
|
|
logger.error("Cannot find the deps install folder!")
|
|
sys.exit(1)
|
|
if not os.path.isdir(KRITA_INSTALL_DIR):
|
|
logger.error("Cannot find the krita install folder!")
|
|
sys.exit(1)
|
|
# Amyspark: paths with spaces are automagically handled by Python!
|
|
|
|
pkg_name = args.package_name
|
|
logger.info(f"Package name is {pkg_name}")
|
|
|
|
pkg_root = f"{os.getcwd()}\\{pkg_name}"
|
|
logger.info(f"Packaging dir is {pkg_root}\n")
|
|
if os.path.isdir(pkg_root):
|
|
logger.error(
|
|
"Packaging dir already exists! Please remove or rename it first.")
|
|
sys.exit(1)
|
|
if os.path.isfile(f"{pkg_root}.zip"):
|
|
logger.error(
|
|
"Packaging zip already exists! Please remove or rename it first.")
|
|
sys.exit(1)
|
|
if os.path.isfile(f"{pkg_root}-dbg.zip"):
|
|
logger.error(
|
|
"Packaging debug zip already exists! Please remove or rename it first.")
|
|
sys.exit(1)
|
|
|
|
if not args.no_interactive:
|
|
status = choice()
|
|
if not status:
|
|
sys.exit(255)
|
|
print("")
|
|
|
|
# Initialize clean PATH
|
|
os.environ["PATH"] = fr"{os.environ['SystemRoot']}\system32;{os.environ['SystemRoot']};{os.environ['SystemRoot']}\System32\Wbem;{os.environ['SystemRoot']}\System32\WindowsPowerShell\v1.0" + "\\"
|
|
os.environ["PATH"] = fr"{os.environ['MINGW_BIN_DIR']};{DEPS_INSTALL_DIR}\bin;{os.environ['PATH']}"
|
|
|
|
|
|
logger.info("")
|
|
logger.info("Trying to guess compiler version...")
|
|
for arg in ("g++", "clang++"):
|
|
commandToRun = f"{arg} --version"
|
|
ret = subprocess.call(commandToRun, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True)
|
|
if ret != 1:
|
|
os.environ['CC'] = arg
|
|
if not os.environ.get('CC'):
|
|
logger.error("No working compiler.")
|
|
sys.exit(1)
|
|
output = subprocess.check_output(f"{os.environ['CC']} --version", shell=True, text=True)
|
|
GCCversionLines = output.splitlines()
|
|
GCC_VERSION_LINE = ""
|
|
for versionLine in GCCversionLines:
|
|
if re.search("g\\+\\+", versionLine):
|
|
GCC_VERSION_LINE = versionLine
|
|
# The space in "clang " is essential to detect
|
|
# the version line and not the InstalledDir
|
|
if not GCC_VERSION_LINE:
|
|
for versionLine in GCCversionLines:
|
|
if re.search("clang ", versionLine):
|
|
GCC_VERSION_LINE = versionLine
|
|
if not GCC_VERSION_LINE:
|
|
logger.error("Failed to get version of g++.")
|
|
sys.exit(1)
|
|
logger.info(f"-- {GCC_VERSION_LINE}")
|
|
IS_TDM = False
|
|
IS_MSYS = False
|
|
IS_CLANG = False
|
|
if re.search("tdm64", GCC_VERSION_LINE):
|
|
logger.info("Compiler looks like TDM64-GCC")
|
|
IS_TDM = True
|
|
if re.search("MSYS", GCC_VERSION_LINE):
|
|
logger.info("Compiler looks like MSYS GCC")
|
|
IS_MSYS = True
|
|
if re.search("clang", GCC_VERSION_LINE):
|
|
logger.info("Compiler looks like Clang")
|
|
IS_CLANG = True
|
|
else:
|
|
logger.info("Compiler looks like plain old MinGW64")
|
|
IS_LLVM_MINGW = False
|
|
IS_MSYS_CLANG = False
|
|
if IS_CLANG:
|
|
# Look for the multiarch target binary dirs. Unfortunately there is
|
|
# no surefire way to detect a llvm-mingw toolchain.
|
|
if os.path.isdir(f"{os.environ['MINGW_BIN_DIR']}\\..\\i686-w64-mingw32\\bin\\") and \
|
|
os.path.isdir(f"{os.environ['MINGW_BIN_DIR']}\\..\\x86_64-w64-mingw32\\bin\\"):
|
|
logger.info("Toolchain looks like llvm-mingw")
|
|
IS_LLVM_MINGW = True
|
|
else:
|
|
logger.info("Toolchain does not look like llvm-mingw, assuming MSYS Clang")
|
|
IS_MSYS_CLANG = True
|
|
|
|
logger.info("")
|
|
logger.info("Trying to guess target architecture...")
|
|
OBJDUMP = CommonUtils.detectObjdump()
|
|
if not OBJDUMP:
|
|
logger.error("objdump is not working.")
|
|
sys.exit(1)
|
|
output = subprocess.check_output(fr"{OBJDUMP} -f {KRITA_INSTALL_DIR}\bin\krita.exe", text=True)
|
|
targetArchLines = output.splitlines()
|
|
|
|
TARGET_ARCH_LINE = ""
|
|
for archLine in targetArchLines:
|
|
if re.search("i386", archLine):
|
|
TARGET_ARCH_LINE = archLine
|
|
if not TARGET_ARCH_LINE:
|
|
logger.info("Possible LLVM objdump, trying to detect architecture...")
|
|
if re.search("coff", archLine):
|
|
TARGET_ARCH_LINE = archLine
|
|
logger.info(f"-- {TARGET_ARCH_LINE}")
|
|
IS_x64 = False
|
|
if not re.search("x86-64", TARGET_ARCH_LINE):
|
|
logger.info("Target looks like x86")
|
|
else:
|
|
logger.info("Target looks like x86_64")
|
|
IS_x64 = True
|
|
|
|
logger.info("")
|
|
logger.info("Testing for objcopy...")
|
|
commandToRun = "objcopy --version"
|
|
try:
|
|
subprocess.check_call(commandToRun, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True)
|
|
except subprocess.CalledProcessError:
|
|
logger.error("objcopy is not working.")
|
|
sys.exit(1)
|
|
|
|
logger.info("")
|
|
logger.info("Testing for strip...")
|
|
commandToRun = "strip --version"
|
|
try:
|
|
subprocess.check_call(commandToRun, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True)
|
|
except subprocess.CalledProcessError:
|
|
logger.error("strip is not working.")
|
|
sys.exit(1)
|
|
|
|
logger.info("")
|
|
logger.info("Trying to guess Qt version...")
|
|
useQt6Build = False
|
|
if os.path.exists(f"{DEPS_INSTALL_DIR}\\bin\\Qt6Core.dll"):
|
|
logger.info(" Found Qt6")
|
|
useQt6Build = True
|
|
else:
|
|
logger.info(" Found Qt5")
|
|
|
|
logger.info("")
|
|
logger.info("Creating base directories...")
|
|
|
|
try:
|
|
os.makedirs(f"{pkg_root}", exist_ok=True)
|
|
os.makedirs(f"{pkg_root}\\bin", exist_ok=True)
|
|
os.makedirs(f"{pkg_root}\\etc", exist_ok=True)
|
|
os.makedirs(f"{pkg_root}\\lib", exist_ok=True)
|
|
os.makedirs(f"{pkg_root}\\share", exist_ok=True)
|
|
except:
|
|
logger.error("Cannot create packaging dir tree!")
|
|
sys.exit(1)
|
|
|
|
logger.info("")
|
|
logger.info("Copying GCC libraries...")
|
|
os.environ['STDLIBS_DIR'] = os.environ['MINGW_BIN_DIR']
|
|
if IS_TDM:
|
|
if not IS_x64:
|
|
# TDM-GCC x86
|
|
os.environ['STDLIBS'] = "libgomp-1"
|
|
else:
|
|
# TDM-GCC x64
|
|
os.environ['STDLIBS'] = "libgomp_64-1"
|
|
elif IS_MSYS:
|
|
if not IS_x64:
|
|
# msys-mingw-w64 x86
|
|
os.environ['STDLIBS'] = "libgcc_s_dw2-1 libgomp-1 libstdc++-6 libwinpthread-1 libatomic-1 libiconv-2 zlib1 libexpat-1 libintl-8 libssl-1_1 libcrypto-1_1"
|
|
else:
|
|
# msys-mingw-w64 x64
|
|
os.environ['STDLIBS'] = "libgcc_s_seh-1 libgomp-1 libstdc++-6 libwinpthread-1 libatomic-1 libiconv-2 zlib1 libexpat-1 libintl-8 libssl-1_1-x64 libcrypto-1_1-x64"
|
|
elif IS_MSYS_CLANG:
|
|
if not IS_x64:
|
|
# msys-mingw-w64-clang64 x86
|
|
os.environ['STDLIBS'] = "libomp libssp-0 libunwind libc++ libwinpthread-1 libiconv-2 zlib1 libexpat-1 libintl-8 libssl-1_1 libcrypto-1_1"
|
|
else:
|
|
# msys-mingw-w64-clang64 x64
|
|
os.environ['STDLIBS'] = "libomp libssp-0 libunwind libc++ libwinpthread-1 libiconv-2 zlib1 libexpat-1 libintl-8 libssl-1_1-x64 libcrypto-1_1-x64"
|
|
elif IS_LLVM_MINGW:
|
|
# llvm-mingw
|
|
os.environ['STDLIBS'] = "libc++ libomp libssp-0 libunwind libwinpthread-1"
|
|
# The toolchain does not include all of the DLLs in the compiler bin dir,
|
|
# so we need to copy them from the cross target bin dir.
|
|
if not IS_x64:
|
|
asanLibName ='libclang_rt.asan_dynamic-i386'
|
|
os.environ['STDLIBS_DIR'] = fr"{os.environ['MINGW_BIN_DIR']}\..\i686-w64-mingw32\bin"
|
|
else:
|
|
asanLibName = 'libclang_rt.asan_dynamic-x86_64'
|
|
os.environ['STDLIBS_DIR'] = fr"{os.environ['MINGW_BIN_DIR']}\..\x86_64-w64-mingw32\bin"
|
|
|
|
output = subprocess.check_output(fr"{OBJDUMP} -p {KRITA_INSTALL_DIR}\bin\krita.exe", text=True)
|
|
|
|
if re.search(asanLibName, output):
|
|
logger.info('The package contains ASAN, packaging the ASAN runtime as well!')
|
|
os.environ['STDLIBS'] = f'{os.environ["STDLIBS"]} {asanLibName}'
|
|
|
|
symbolizerBinaries = ['libLLVM-18.dll', 'llvm-symbolizer.exe']
|
|
for file in symbolizerBinaries:
|
|
filePath = os.path.join(os.environ['MINGW_BIN_DIR'], file)
|
|
|
|
logger.info(f'Copying symbolizer lib: {filePath}')
|
|
shutil.copy(filePath, fr"{pkg_root}\bin")
|
|
else:
|
|
logger.info('No ASAN was found in the Krita binary, skipping its packaging...')
|
|
|
|
else:
|
|
if not IS_x64:
|
|
# mingw-w64 x86
|
|
os.environ['STDLIBS'] = "libgcc_s_dw2-1 libgomp-1 libstdc++-6 libwinpthread-1"
|
|
else:
|
|
# mingw-w64 x64
|
|
os.environ['STDLIBS'] = "libgcc_s_seh-1 libgomp-1 libstdc++-6 libwinpthread-1"
|
|
|
|
for lib in os.environ['STDLIBS'].split(" "):
|
|
libPath = fr"{os.environ['STDLIBS_DIR']}\{lib}.dll"
|
|
# libssp-0 is not present in clang18
|
|
if lib != 'libssp-0' or os.path.isfile(libPath):
|
|
shutil.copy(libPath, fr"{pkg_root}\bin")
|
|
|
|
logger.info("")
|
|
logger.info("Copying files...")
|
|
# krita.exe
|
|
shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\krita.exe", f"{pkg_root}\\bin\\")
|
|
shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\krita.com", f"{pkg_root}\\bin\\")
|
|
if os.path.isfile(f"{KRITA_INSTALL_DIR}\\bin\\krita.pdb"):
|
|
shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\krita.pdb", f"{pkg_root}\\bin\\")
|
|
# kritarunner.exe
|
|
if os.path.isfile(f"{KRITA_INSTALL_DIR}\\bin\\kritarunner.exe"):
|
|
shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\kritarunner.exe", f"{pkg_root}\\bin\\")
|
|
if os.path.isfile(f"{KRITA_INSTALL_DIR}\\bin\\kritarunner.pdb"):
|
|
shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\kritarunner.pdb",
|
|
f"{pkg_root}\\bin\\")
|
|
shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\kritarunner.com",
|
|
f"{pkg_root}\\bin\\")
|
|
if os.path.isfile(f"{KRITA_INSTALL_DIR}\\bin\\FreehandStrokeBenchmark.exe"):
|
|
shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\FreehandStrokeBenchmark.exe", f"{pkg_root}\\bin\\")
|
|
MergeFolders.merge_folders(f"{DEPS_INSTALL_DIR}\\bin\\data\\", f"{pkg_root}\\bin\\data\\")
|
|
|
|
# qt.conf -- to specify the location to Qt translations
|
|
shutil.copy(fr"{KRITA_SRC_DIR}\packaging\windows\qt.conf", fr"{pkg_root}\bin")
|
|
# DLLs from bin/
|
|
logger.info("Copying all DLLs except Qt5/Qt6 * from bin/")
|
|
files = glob.glob(f"{KRITA_INSTALL_DIR}\\bin\\*.dll")
|
|
pdbs = glob.glob(f"{KRITA_INSTALL_DIR}\\bin\\*.pdb")
|
|
for f in itertools.chain(files, pdbs):
|
|
if not os.path.basename(f).startswith("Qt5") and not os.path.basename(f).startswith("Qt6"):
|
|
shutil.copy(f, f"{pkg_root}\\bin")
|
|
files = glob.glob(f"{DEPS_INSTALL_DIR}\\bin\\*.dll")
|
|
for f in files:
|
|
pdb = f"{os.path.dirname(f)}\\{os.path.splitext(os.path.basename(f))[0]}.pdb"
|
|
if not os.path.basename(f).startswith("Qt5") and not os.path.basename(f).startswith("Qt6"):
|
|
shutil.copy(f, f"{pkg_root}\\bin")
|
|
if os.path.isfile(pdb):
|
|
shutil.copy(pdb, f"{pkg_root}\\bin")
|
|
# symsrv.yes for Dr. Mingw
|
|
shutil.copy(f"{DEPS_INSTALL_DIR}\\bin\\symsrv.yes", f"{pkg_root}\\bin")
|
|
# DLLs from lib/
|
|
logger.info("Copying all DLLs from lib/ (deps)")
|
|
files = glob.glob(fr"{DEPS_INSTALL_DIR}\lib\*.dll")
|
|
for f in files:
|
|
shutil.copy(f, fr"{pkg_root}\bin")
|
|
for f in files:
|
|
shutil.copy(f, fr"{pkg_root}\bin")
|
|
# KF5/KF6 plugins may be placed at different locations depending on how Qt is built
|
|
MergeFolders.merge_folders(f"{DEPS_INSTALL_DIR}\\lib\\plugins\\imageformats\\", f"{pkg_root}\\bin\\imageformats\\")
|
|
MergeFolders.merge_folders(f"{DEPS_INSTALL_DIR}\\plugins\\imageformats\\", f"{pkg_root}\\bin\\imageformats\\")
|
|
|
|
if useQt6Build:
|
|
MergeFolders.merge_folders(f"{DEPS_INSTALL_DIR}\\plugins\\kf6\\", f"{pkg_root}\\bin\\kf6\\")
|
|
else:
|
|
MergeFolders.merge_folders(f"{DEPS_INSTALL_DIR}\\plugins\\kf5\\", f"{pkg_root}\\bin\\kf5\\")
|
|
|
|
# Copy the sql drivers explicitly
|
|
MergeFolders.merge_folders(f"{DEPS_INSTALL_DIR}\\plugins\\sqldrivers\\", f"{pkg_root}\\bin\\sqldrivers")
|
|
|
|
# Qt Translations
|
|
# it seems that windeployqt does these, but only * some * of these???
|
|
os.makedirs(f"{pkg_root}\\bin\\translations", exist_ok=True)
|
|
files = glob.glob(f"{DEPS_INSTALL_DIR}\\translations\\qt_*.qm")
|
|
for f in files:
|
|
# Exclude qt_help_*.qm
|
|
if not os.path.basename(f).startswith("qt_help"):
|
|
shutil.copy(f, f"{pkg_root}\\bin\\translations")
|
|
|
|
# Krita plugins
|
|
MergeFolders.merge_folders(f"{KRITA_INSTALL_DIR}\\lib\\kritaplugins\\", f"{pkg_root}\\lib\\kritaplugins\\")
|
|
|
|
if os.path.isdir(f"{DEPS_INSTALL_DIR}\\lib\\krita-python-libs"):
|
|
MergeFolders.merge_folders(f"{DEPS_INSTALL_DIR}\\lib\\krita-python-libs", f"{pkg_root}\\lib\\krita-python-libs")
|
|
if os.path.isdir(f"{KRITA_INSTALL_DIR}\\lib\\krita-python-libs"):
|
|
MergeFolders.merge_folders(f"{KRITA_INSTALL_DIR}\\lib\\krita-python-libs", f"{pkg_root}\\lib\\krita-python-libs")
|
|
if os.path.isdir(f"{DEPS_INSTALL_DIR}\\lib\\site-packages"):
|
|
MergeFolders.merge_folders(f"{DEPS_INSTALL_DIR}\\lib\\site-packages", f"{pkg_root}\\lib\\site-packages")
|
|
|
|
# MLT plugins and their data
|
|
MergeFolders.merge_folders(f"{DEPS_INSTALL_DIR}\\lib\\mlt", f"{pkg_root}\\lib\\mlt")
|
|
MergeFolders.merge_folders(f"{DEPS_INSTALL_DIR}\\share\\mlt", f"{pkg_root}\\share\\mlt")
|
|
|
|
# Fontconfig
|
|
MergeFolders.merge_folders(f"{DEPS_INSTALL_DIR}\\etc\\fonts", f"{pkg_root}\\etc\\fonts")
|
|
|
|
# Share
|
|
MergeFolders.merge_folders(f"{KRITA_INSTALL_DIR}\\share\\color", f"{pkg_root}\\share\\color")
|
|
MergeFolders.merge_folders(f"{KRITA_INSTALL_DIR}\\share\\color-schemes", f"{pkg_root}\\share\\color-schemes")
|
|
MergeFolders.merge_folders(f"{KRITA_INSTALL_DIR}\\share\\icons", f"{pkg_root}\\share\\icons")
|
|
MergeFolders.merge_folders(f"{KRITA_INSTALL_DIR}\\share\\krita", f"{pkg_root}\\share\\krita")
|
|
MergeFolders.merge_folders(f"{KRITA_INSTALL_DIR}\\share\\kritaplugins", f"{pkg_root}\\share\\kritaplugins")
|
|
|
|
if useQt6Build:
|
|
MergeFolders.merge_folders(f"{DEPS_INSTALL_DIR}\\share\\kf6", f"{pkg_root}\\share\\kf6")
|
|
else:
|
|
MergeFolders.merge_folders(f"{DEPS_INSTALL_DIR}\\share\\kf5", f"{pkg_root}\\share\\kf5")
|
|
|
|
MergeFolders.merge_folders(f"{DEPS_INSTALL_DIR}\\share\\mime", f"{pkg_root}\\share\\mime")
|
|
# Python libs are copied by share\krita above
|
|
# Copy locale to bin
|
|
MergeFolders.merge_folders(f"{KRITA_INSTALL_DIR}\\share\\locale", f"{pkg_root}\\bin\\locale")
|
|
MergeFolders.merge_folders(f"{DEPS_INSTALL_DIR}\\share\\locale", f"{pkg_root}\\bin\\locale")
|
|
|
|
# Copy shortcut link from source (can't create it dynamically)
|
|
shutil.copy(f"{KRITA_SRC_DIR}\\packaging\\windows\\krita.lnk", pkg_root)
|
|
shutil.copy(
|
|
f"{KRITA_SRC_DIR}\\packaging\\windows\\krita-minimal.lnk", pkg_root)
|
|
shutil.copy(
|
|
f"{KRITA_SRC_DIR}\\packaging\\windows\\krita-animation.lnk", pkg_root)
|
|
|
|
# QML deployment:
|
|
#
|
|
# When deploying QML modules we should pass windeployqt all the folders
|
|
# **in the source tree** where our resource-embedded .qml files are situated.
|
|
# Every such folder should be declared with --qmldir option. These files will
|
|
# **not** be deployed (because they are expected to be stored as binary resources),
|
|
# but all their dependencies will be deployed to the package folder.
|
|
#
|
|
# Multiple QML dependencies search paths can be provided by --qmlimport switch,
|
|
# we don't pass it explicitly and let it be deduced by windeployqt using qtpath
|
|
# executable.
|
|
|
|
# Here we should list all the folders/plugins in Krita that have
|
|
# .qml files inside. Theoretically, we can just pass the entire Krita's
|
|
# source tree, but I'm not sure it is a good idea.
|
|
QMLDIR_ARGS = ["--qmldir", fr"{KRITA_SRC_DIR}\plugins\dockers\textproperties"]
|
|
|
|
# A safeguard for the case when KDE_INSTALL_USE_QT_SYS_PATHS is not properly
|
|
# activated on Windows and the QML modules are installed into a default KDE's
|
|
# location instead of the one returned by qtpath. If you see this error you
|
|
# should either recreate your build tree, or pass -DKDE_INSTALL_USE_QT_SYS_PATHS=ON
|
|
# to the build or check if you have non-standard qt.conf in your installation
|
|
# root.
|
|
|
|
if os.path.isdir(fr"{KRITA_INSTALL_DIR}\lib\qml"):
|
|
logger.error("Some of Krita's QML modules were installed in an incorrect location")
|
|
logger.error(fr" actual path: {KRITA_INSTALL_DIR}\lib\qml")
|
|
logger.error(fr" expected path: {KRITA_INSTALL_DIR}\qml")
|
|
exit(103)
|
|
|
|
if os.path.isdir(fr"{DEPS_INSTALL_DIR}\lib\qml"):
|
|
logger.error("Some of Deps' QML modules were installed in an incorrect location")
|
|
logger.error(fr" actual path: {DEPS_INSTALL_DIR}\lib\qml")
|
|
logger.error(fr" expected path: {DEPS_INSTALL_DIR}\qml")
|
|
exit(103)
|
|
|
|
# windeployqt
|
|
if useQt6Build:
|
|
# NOTE: we don't pass `--release` option to activate autodetection of the build type
|
|
# (which will effectively accept any kind of the binaries unless on MSVC)
|
|
|
|
verboseOption = ["-verbose", "2"] if useVerbosePackagingLog else []
|
|
run_subprocess_checked(
|
|
["windeployqt.exe", *QMLDIR_ARGS,
|
|
"-gui", "-core", "-core5compat", "-openglwidgets", "-svgwidgets", "-opengl",
|
|
"-concurrent", "-network", "-printsupport", "-svg",
|
|
# don't ship Windows Vista and Windows 11 styles if they happen to be present
|
|
"--skip-plugin-types", "styles",
|
|
"-xml", "-sql", "-qml", "-quick", "-quickwidgets", *verboseOption,
|
|
f"{pkg_root}\\bin\\krita.exe", f"{pkg_root}\\bin\\krita.dll"])
|
|
else:
|
|
verboseOption = ["-verbose", "2"] if useVerbosePackagingLog else []
|
|
run_subprocess_checked(
|
|
["windeployqt.exe", *QMLDIR_ARGS, "--release", "-gui", "-core", "-concurrent", "-network", "-printsupport", "-svg",
|
|
"-xml", "-sql", "-qml", "-quick", "-quickwidgets", *verboseOption,
|
|
f"{pkg_root}\\bin\\krita.exe", f"{pkg_root}\\bin\\krita.dll"])
|
|
|
|
# Qt5 version of windeployqt.exe cannot skip plugin types, so we
|
|
# should remove this style manually
|
|
if os.path.exists(fr"{pkg_root}\bin\styles\qwindowsvistastyle.dll"):
|
|
os.remove(fr"{pkg_root}\bin\styles\qwindowsvistastyle.dll")
|
|
|
|
# verify that broken Windows Vista and Windows11 styles are **not** deployed!
|
|
deployedStyles = glob.glob(fr"{pkg_root}\bin\styles\*")
|
|
if deployedStyles:
|
|
logger.error("Some custom style plugins have been deployed! Not supported by Krita!")
|
|
for style in deployedStyles:
|
|
logger.error(fr" {style}")
|
|
exit(104)
|
|
|
|
# ffmpeg
|
|
if os.path.exists(f"{DEPS_INSTALL_DIR}\\bin\\ffmpeg.exe"):
|
|
shutil.copy(f"{DEPS_INSTALL_DIR}\\bin\\ffmpeg.exe", f"{pkg_root}\\bin")
|
|
shutil.copy(f"{DEPS_INSTALL_DIR}\\bin\\ffprobe.exe", f"{pkg_root}\\bin")
|
|
if os.path.exists(f"{DEPS_INSTALL_DIR}\\bin\\ffmpeg_LICENSE.txt"):
|
|
shutil.copy(f"{DEPS_INSTALL_DIR}\\bin\\ffmpeg_LICENSE.txt",
|
|
f"{pkg_root}\\bin")
|
|
if os.path.exists(f"{DEPS_INSTALL_DIR}\\bin\\ffmpeg_README.txt"):
|
|
shutil.copy(f"{DEPS_INSTALL_DIR}\\bin\\ffmpeg_README.txt",
|
|
f"{pkg_root}\\bin")
|
|
|
|
# Copy embedded Python
|
|
MergeFolders.merge_folders(f"{DEPS_INSTALL_DIR}\\python", f"{pkg_root}\\python")
|
|
if os.path.exists(f"{pkg_root}\\python\\python.exe"):
|
|
os.remove(f"{pkg_root}\\python\\python.exe")
|
|
if os.path.exists(f"{pkg_root}\\python\\pythonw.exe"):
|
|
os.remove(f"{pkg_root}\\python\\pythonw.exe")
|
|
|
|
# Remove Python cache files
|
|
logger.info("")
|
|
logger.info("Deleting python cache...")
|
|
for d in os.walk(pkg_root):
|
|
pycache = f"{d[0]}\\__pycache__"
|
|
if os.path.isdir(pycache):
|
|
logger.debug(f"Deleting Python cache {pycache}")
|
|
shutil.rmtree(pycache)
|
|
|
|
if os.path.exists(f"{pkg_root}\\lib\\site-packages"):
|
|
logger.info(f"Deleting unnecessary Python packages")
|
|
for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\packaging*"):
|
|
shutil.rmtree(f)
|
|
for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\pip*"):
|
|
shutil.rmtree(f)
|
|
for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\ply*"):
|
|
shutil.rmtree(f)
|
|
for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\pyparsing*"):
|
|
shutil.rmtree(f)
|
|
for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\PyQt_builder*"):
|
|
shutil.rmtree(f)
|
|
for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\setuptools.pth"):
|
|
os.remove(f)
|
|
for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\setuptools*"):
|
|
shutil.rmtree(f)
|
|
for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\sip*"):
|
|
shutil.rmtree(f)
|
|
for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\toml*"):
|
|
shutil.rmtree(f)
|
|
for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\meson*"):
|
|
shutil.rmtree(f)
|
|
for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\easy-install.pth"):
|
|
os.remove(f)
|
|
|
|
if not CommonUtils.boolFromEnv(os.environ.get('KRITACI_SKIP_SPLIT_DEBUG', '0')):
|
|
run_subprocess_checked(
|
|
[sys.executable,
|
|
"-u",
|
|
os.path.join(os.path.dirname(__file__), "split-debug-info.py"),
|
|
"-p", "Windows",
|
|
pkg_root
|
|
])
|
|
|
|
if args.pre_zip_hook:
|
|
logger.info("Running pre-zip hook...")
|
|
if args.pre_zip_hook.endswith('.cmd'):
|
|
run_subprocess_checked(
|
|
["cmd", "/c", args.pre_zip_hook, f"{pkg_root}\\"])
|
|
elif args.pre_zip_hook.endswith('.py'):
|
|
run_subprocess_checked(
|
|
[sys.executable, "-u", args.pre_zip_hook, f"{pkg_root}\\"])
|
|
else:
|
|
logger.error("pre-zip hook has unknown format!")
|
|
sys.exit(102)
|
|
|
|
logger.info("")
|
|
logger.info("Packaging stripped binaries...")
|
|
run_subprocess_checked(
|
|
[os.environ["SEVENZIP_EXE"], "a", "-tzip",
|
|
f"{pkg_name}.zip", f"{pkg_root}\\", "-xr!*.debug"])
|
|
|
|
if not CommonUtils.boolFromEnv(os.environ.get('KRITACI_SKIP_DEBUG_PACKAGE', '0')):
|
|
logger.info("")
|
|
logger.info("Packaging debug info...")
|
|
# (note that the top-level package dir is not included)
|
|
run_subprocess_checked(
|
|
[os.environ["SEVENZIP_EXE"], "a", "-tzip",
|
|
f"{pkg_name}-dbg.zip", "-r", f"{pkg_root}\\*.debug"])
|
|
|
|
logger.info("")
|
|
logger.info(f"Krita packaged as {pkg_name}.zip")
|
|
if os.path.isfile(f"{pkg_name}-dbg.zip"):
|
|
logger.info(f"Debug info packaged as {pkg_name}-dbg.zip")
|
|
logger.info(f"Packaging dir is {pkg_root}")
|
|
logger.info("NOTE: Do not create installer with packaging dir. Extract from")
|
|
logger.info(f" {pkg_name}.zip instead")
|
|
logger.info("and do _not_ run krita inside the extracted directory because it will")
|
|
logger.info(" create extra unnecessary files.\n")
|
|
logger.info("Please remember to actually test the package before releasing it.\n")
|