File indexing completed on 2024-05-12 04:28:29
0001 #!/usr/bin/env python 0002 0003 # SPDX-FileCopyrightText: 2021 L. E. Segovia <amy@amyspark.me> 0004 # SPDX-License-Identifier: GPL-2.0-or-later 0005 0006 # This Python script is meant to prepare a Krita package folder to be zipped or 0007 # to be a base for the installer. 0008 0009 import argparse 0010 import glob 0011 import itertools 0012 import os 0013 import re 0014 import pathlib 0015 import shutil 0016 import subprocess 0017 import sys 0018 import warnings 0019 0020 0021 # Subroutines 0022 0023 def choice(prompt="Is this ok?"): 0024 c = input(f"{prompt} [y/n] ") 0025 if c == "y": 0026 return True 0027 else: 0028 return False 0029 0030 0031 def find_on_path(variable, executable): 0032 os.environ[variable] = shutil.which(executable) 0033 0034 0035 def prompt_for_dir(prompt): 0036 user_input = input(f"{prompt} ") 0037 if not len(user_input): 0038 return None 0039 result = os.path.exists(user_input) 0040 if result is None: 0041 print("Input does not point to valid dir!") 0042 return prompt_for_dir(prompt) 0043 else: 0044 return os.path.realpath(user_input) 0045 0046 0047 print("Krita Windows packaging script (MSVC version)") 0048 0049 0050 # command-line args parsing 0051 parser = argparse.ArgumentParser() 0052 0053 basic_options = parser.add_argument_group("Basic options") 0054 basic_options.add_argument("--no-interactive", action='store_true', 0055 help="Run without interactive prompts. When not specified, the script will prompt for some of the parameters.") 0056 basic_options.add_argument( 0057 "--package-name", action='store', help="Specify the package name", required=True) 0058 0059 path_options = parser.add_argument_group("Path options") 0060 path_options.add_argument("--src-dir", action='store', 0061 help="Specify Krita source dir. If unspecified, this will be determined from the script location") 0062 path_options.add_argument("--deps-install-dir", action='store', 0063 help="Specify deps install dir") 0064 path_options.add_argument("--krita-install-dir", action='store', 0065 help="Specify Krita install dir") 0066 0067 special_options = parser.add_argument_group("Special options") 0068 special_options.add_argument("--pre-zip-hook", action='store', 0069 help="Specify a script to be called before packaging the zip archive, can be used to sign the binaries") 0070 0071 args = parser.parse_args() 0072 0073 # Check environment config 0074 0075 if os.environ.get("SEVENZIP_EXE") is None: 0076 find_on_path("SEVENZIP_EXE", "7z.exe") 0077 if os.environ.get("SEVENZIP_EXE") is None: 0078 find_on_path("SEVENZIP_EXE", "7za.exe") 0079 if os.environ.get("SEVENZIP_EXE") is None: 0080 os.environ["SEVENZIP_EXE"] = f"{os.environ['ProgramFiles']}\\7-Zip\\7-Z.exe" 0081 if not os.path.isfile(os.environ["SEVENZIP_EXE"]): 0082 os.environ["SEVENZIP_EXE"] = "{}\\7-Zip\\7-Z.exe".format( 0083 os.environ["ProgramFiles(x86)"]) 0084 if not os.path.isfile(os.environ["SEVENZIP_EXE"]): 0085 warnings.warn("7-Zip not found!") 0086 exit(102) 0087 print(f"7-Zip: {os.environ['SEVENZIP_EXE']}") 0088 0089 HAVE_FXC_EXE = None 0090 0091 # Windows SDK is needed for windeployqt to get d3dcompiler_xx.dll 0092 if os.environ.get("WindowsSdkDir") is None and os.environ.get("ProgramFiles(x86)") is not None: 0093 os.environ["WindowsSdkDir"] = "{}\\Windows Kits\\10".format( 0094 os.environ["ProgramFiles(x86)"]) 0095 if os.path.isdir(os.environ["WindowsSdkDir"]): 0096 f = os.environ["WindowsSdkDir"] 0097 if os.path.isfile(f"{f}\\bin\\d\\fxc.exe"): 0098 os.environ["HAVE_FXC_EXE"] = True 0099 else: 0100 delims = glob.glob(f"{f}\\bin\\10.*") 0101 for f in delims: 0102 if os.path.isfile(f"{f}\\x64\\fxc.exe"): 0103 HAVE_FXC_EXE = True 0104 if HAVE_FXC_EXE is None: 0105 os.environ["WindowsSdkDir"] = None 0106 warnings.warn("Windows SDK 10 with fxc.exe not found") 0107 warnings.warn( 0108 "If Qt was built with ANGLE (dynamic OpenGL) support, the package might not work properly on some systems!") 0109 else: 0110 print( 0111 f"Windows SDK 10 with fxc.exe found on {os.environ['WindowsSdkDir']}") 0112 0113 KRITA_SRC_DIR = None 0114 0115 if args.src_dir is not None: 0116 KRITA_SRC_DIR = args.src_dir 0117 0118 if KRITA_SRC_DIR is None: 0119 _temp = sys.argv[0] 0120 if os.path.dirname(_temp).endswith("\\packaging\\windows"): 0121 _base = pathlib.PurePath(_temp) 0122 if os.path.isfile(f"{_base.parents[2]}\\CMakeLists.txt"): 0123 if os.path.isfile(f"{_base.parents[2]}\\3rdparty\\CMakeLists.txt"): 0124 KRITA_SRC_DIR = os.path.realpath(_base.parents[2]) 0125 print("Script is running inside Krita source dir") 0126 0127 if KRITA_SRC_DIR is None: 0128 if args.no_interactive: 0129 KRITA_SRC_DIR = prompt_for_dir("Provide path of Krita src dir") 0130 if KRITA_SRC_DIR is None: 0131 warnings.warn("ERROR: Krita src dir not found!") 0132 exit(102) 0133 print(f"Krita src: {KRITA_SRC_DIR}") 0134 0135 DEPS_INSTALL_DIR = None 0136 0137 if args.deps_install_dir is not None: 0138 DEPS_INSTALL_DIR = args.deps_install_dir 0139 if DEPS_INSTALL_DIR is None: 0140 DEPS_INSTALL_DIR = f"{os.getcwd()}\\i_deps" 0141 print(f"Using default deps install dir: {DEPS_INSTALL_DIR}") 0142 if not args.no_interactive: 0143 status = choice() 0144 if not status: 0145 DEPS_INSTALL_DIR = prompt_for_dir( 0146 "Provide path of deps install dir") 0147 if DEPS_INSTALL_DIR is None: 0148 warnings.warn("ERROR: Deps install dir not set!") 0149 exit(102) 0150 print(f"Deps install dir: {DEPS_INSTALL_DIR}") 0151 0152 KRITA_INSTALL_DIR = None 0153 0154 if args.krita_install_dir is not None: 0155 KRITA_INSTALL_DIR = args.krita_install_dir 0156 if KRITA_INSTALL_DIR is None: 0157 KRITA_INSTALL_DIR = f"{os.getcwd()}\\i" 0158 print(f"Using default Krita install dir: {KRITA_INSTALL_DIR}") 0159 if not args.no_interactive: 0160 status = choice() 0161 if not status: 0162 KRITA_INSTALL_DIR = prompt_for_dir( 0163 "Provide path of Krita install dir") 0164 if KRITA_INSTALL_DIR is None: 0165 warnings.warn("ERROR: Krita install dir not set!") 0166 exit(102) 0167 print(f"Krita install dir: {KRITA_INSTALL_DIR}") 0168 0169 # Simple checking 0170 if not os.path.isdir(DEPS_INSTALL_DIR): 0171 warnings.warn("ERROR: Cannot find the deps install folder!") 0172 exit(1) 0173 if not os.path.isdir(KRITA_INSTALL_DIR): 0174 warnings.warn("ERROR: Cannot find the krita install folder!") 0175 exit(1) 0176 # Amyspark: paths with spaces are automagically handled by Python! 0177 0178 pkg_name = args.package_name 0179 print(f"Package name is {pkg_name}") 0180 0181 pkg_root = f"{os.getcwd()}\\{pkg_name}" 0182 print(f"Packaging dir is {pkg_root}\n") 0183 if os.path.isdir(pkg_root): 0184 warnings.warn( 0185 "ERROR: Packaging dir already exists! Please remove or rename it first.") 0186 exit(1) 0187 if os.path.isfile(f"{pkg_root}.zip"): 0188 warnings.warn( 0189 "ERROR: Packaging zip already exists! Please remove or rename it first.") 0190 exit(1) 0191 if os.path.isfile(f"{pkg_root}-dbg.zip"): 0192 warnings.warn( 0193 "ERROR: Packaging debug zip already exists! Please remove or rename it first.") 0194 exit(1) 0195 0196 if not args.no_interactive: 0197 status = choice() 0198 if not status: 0199 exit(255) 0200 print("") 0201 0202 # Initialize PATH 0203 os.environ["PATH"] = f"{DEPS_INSTALL_DIR}\\bin;{os.environ['PATH']}" 0204 0205 print("\nThis is the packaging script for MSVC.\n") 0206 0207 print("\nCreating base directories...") 0208 0209 try: 0210 os.makedirs(f"{pkg_root}", exist_ok=True) 0211 os.makedirs(f"{pkg_root}\\bin", exist_ok=True) 0212 os.makedirs(f"{pkg_root}\\etc", exist_ok=True) 0213 os.makedirs(f"{pkg_root}\\lib", exist_ok=True) 0214 os.makedirs(f"{pkg_root}\\share", exist_ok=True) 0215 except: 0216 warnings.warn("ERROR: Cannot create packaging dir tree!") 0217 exit(1) 0218 0219 print("\nCopying files...") 0220 # krita.exe 0221 shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\krita.exe", f"{pkg_root}\\bin\\") 0222 shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\krita.com", f"{pkg_root}\\bin\\") 0223 if os.path.isfile(f"{KRITA_INSTALL_DIR}\\bin\\krita.pdb"): 0224 shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\krita.pdb", f"{pkg_root}\\bin\\") 0225 # kritarunner.exe 0226 shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\kritarunner.exe", f"{pkg_root}\\bin\\") 0227 if os.path.isfile(f"{KRITA_INSTALL_DIR}\\bin\\kritarunner.pdb"): 0228 shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\kritarunner.pdb", 0229 f"{pkg_root}\\bin\\") 0230 shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\kritarunner_com.com", 0231 f"{pkg_root}\\bin\\") 0232 0233 if os.path.isfile(f"{KRITA_INSTALL_DIR}\\bin\\FreehandStrokeBenchmark.exe"): 0234 shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\FreehandStrokeBenchmark.exe", f"{pkg_root}\\bin\\") 0235 subprocess.run(["xcopy", "/S", "/Y", "/I", 0236 f"{DEPS_INSTALL_DIR}\\bin\\data\\", f"{pkg_root}\\bin\\data\\"]) 0237 0238 # DLLs from bin/ 0239 print("INFO: Copying all DLLs except Qt5 * from bin/") 0240 files = glob.glob(f"{KRITA_INSTALL_DIR}\\bin\\*.dll") 0241 pdbs = glob.glob(f"{KRITA_INSTALL_DIR}\\bin\\*.pdb") 0242 for f in itertools.chain(files, pdbs): 0243 if not os.path.basename(f).startswith("Qt5"): 0244 shutil.copy(f, f"{pkg_root}\\bin") 0245 files = glob.glob(f"{DEPS_INSTALL_DIR}\\bin\\*.dll") 0246 for f in files: 0247 pdb = f"{os.path.dirname(f)}\\{os.path.splitext(os.path.basename(f))[0]}.pdb" 0248 if not os.path.basename(f).startswith("Qt5"): 0249 shutil.copy(f, f"{pkg_root}\\bin") 0250 if os.path.isfile(pdb): 0251 shutil.copy(pdb, f"{pkg_root}\\bin") 0252 # symsrv.yes for Dr. Mingw 0253 shutil.copy(f"{DEPS_INSTALL_DIR}\\bin\\symsrv.yes", f"{pkg_root}\\bin") 0254 # KF5 plugins may be placed at different locations depending on how Qt is built 0255 subprocess.run(["xcopy", "/S", "/Y", "/I", 0256 f"{DEPS_INSTALL_DIR}\\lib\\plugins\\imageformats\\", f"{pkg_root}\\bin\\imageformats\\"]) 0257 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\plugins\\imageformats\\".format( 0258 DEPS_INSTALL_DIR), f"{pkg_root}\\bin\\imageformats\\"]) 0259 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\plugins\\kf5\\".format( 0260 DEPS_INSTALL_DIR), f"{pkg_root}\\bin\\kf5\\"]) 0261 0262 # Copy the sql drivers explicitly 0263 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\plugins\\sqldrivers\\".format( 0264 DEPS_INSTALL_DIR), f"{pkg_root}\\bin\\sqldrivers"], check=True) 0265 0266 # Qt Translations 0267 # it seems that windeployqt does these, but only * some * of these??? 0268 os.makedirs(f"{pkg_root}\\bin\\translations", exist_ok=True) 0269 files = glob.glob(f"{DEPS_INSTALL_DIR}\\translations\\qt_*.qm") 0270 for f in files: 0271 # Exclude qt_help_*.qm 0272 if not os.path.basename(f).startswith("qt_help"): 0273 shutil.copy(f, f"{pkg_root}\\bin\\translations") 0274 0275 # Krita plugins 0276 subprocess.run(["xcopy", "/Y", "{}\\lib\\kritaplugins\\*.dll".format( 0277 KRITA_INSTALL_DIR), f"{pkg_root}\\lib\\kritaplugins\\"], check=True) 0278 subprocess.run(["xcopy", "/Y", "{}\\lib\\kritaplugins\\*.pdb".format( 0279 KRITA_INSTALL_DIR), f"{pkg_root}\\lib\\kritaplugins\\"], check=True) 0280 if os.path.isdir(f"{DEPS_INSTALL_DIR}\\lib\\krita-python-libs"): 0281 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\lib\\krita-python-libs".format(DEPS_INSTALL_DIR), f"{pkg_root}\\lib\\krita-python-libs"], check=True) 0282 if os.path.isdir(f"{KRITA_INSTALL_DIR}\\lib\\krita-python-libs"): 0283 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\lib\\krita-python-libs".format( 0284 KRITA_INSTALL_DIR), f"{pkg_root}\\lib\\krita-python-libs"], check=True) 0285 if os.path.isdir(f"{DEPS_INSTALL_DIR}\\lib\\site-packages"): 0286 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\lib\\site-packages".format( 0287 DEPS_INSTALL_DIR), f"{pkg_root}\\lib\\site-packages"], check=True) 0288 0289 # MLT plugins and their data 0290 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\lib\\mlt".format( 0291 DEPS_INSTALL_DIR), f"{pkg_root}\\lib\\mlt"], check=True) 0292 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\mlt".format( 0293 DEPS_INSTALL_DIR), f"{pkg_root}\\share\\mlt"], check=True) 0294 0295 # Fontconfig 0296 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\etc\\fonts".format( 0297 DEPS_INSTALL_DIR), f"{pkg_root}\\etc\\fonts"], check=True) 0298 0299 # Share 0300 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\color".format( 0301 KRITA_INSTALL_DIR), f"{pkg_root}\\share\\color"], check=True) 0302 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\color-schemes".format( 0303 KRITA_INSTALL_DIR), f"{pkg_root}\\share\\color-schemes"], check=True) 0304 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\icons".format( 0305 KRITA_INSTALL_DIR), f"{pkg_root}\\share\\icons"], check=True) 0306 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\krita".format( 0307 KRITA_INSTALL_DIR), f"{pkg_root}\\share\\krita"]) 0308 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\kritaplugins".format( 0309 KRITA_INSTALL_DIR), f"{pkg_root}\\share\\kritaplugins"], check=True) 0310 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\kf5".format( 0311 DEPS_INSTALL_DIR), f"{pkg_root}\\share\\kf5"], check=True) 0312 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\mime".format( 0313 DEPS_INSTALL_DIR), f"{pkg_root}\\share\\mime"], check=True) 0314 # Python libs are copied by share\krita above 0315 # Copy locale to bin 0316 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\locale".format( 0317 KRITA_INSTALL_DIR), f"{pkg_root}\\bin\\locale"]) 0318 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\locale".format( 0319 DEPS_INSTALL_DIR), f"{pkg_root}\\bin\\locale"], check=True) 0320 0321 # Copy shortcut link from source (can't create it dynamically) 0322 shutil.copy(f"{KRITA_SRC_DIR}\\packaging\\windows\\krita.lnk", pkg_root) 0323 shutil.copy( 0324 f"{KRITA_SRC_DIR}\\packaging\\windows\\krita-minimal.lnk", pkg_root) 0325 shutil.copy( 0326 f"{KRITA_SRC_DIR}\\packaging\\windows\\krita-animation.lnk", pkg_root) 0327 0328 QMLDIR_ARGS = ["--qmldir", f"{DEPS_INSTALL_DIR}\\qml"] 0329 if os.path.isdir(f"{KRITA_INSTALL_DIR}\\lib\\qml"): 0330 subprocess.run(["xcopy", "/S", "/Y", "/I", 0331 f"{KRITA_INSTALL_DIR}\\lib\\qml", f"{pkg_root}\\bin\\"], check=True) 0332 # This doesn't really seem to do anything 0333 QMLDIR_ARGS.extend(["--qmldir", f"{KRITA_INSTALL_DIR}\\lib\\qml"]) 0334 0335 # windeployqt 0336 subprocess.run(["windeployqt.exe", *QMLDIR_ARGS, "--release", "-gui", "-core", "-concurrent", "-network", "-printsupport", "-svg", 0337 "-xml", "-sql", "-qml", "-quick", "-quickwidgets", f"{pkg_root}\\bin\\krita.exe", f"{pkg_root}\\bin\\krita.dll"], check=True) 0338 0339 # ffmpeg 0340 if os.path.exists(f"{DEPS_INSTALL_DIR}\\bin\\ffmpeg.exe"): 0341 shutil.copy(f"{DEPS_INSTALL_DIR}\\bin\\ffmpeg.exe", f"{pkg_root}\\bin") 0342 shutil.copy(f"{DEPS_INSTALL_DIR}\\bin\\ffprobe.exe", f"{pkg_root}\\bin") 0343 shutil.copy(f"{DEPS_INSTALL_DIR}\\bin\\ffmpeg_LICENSE.txt", 0344 f"{pkg_root}\\bin") 0345 shutil.copy(f"{DEPS_INSTALL_DIR}\\bin\\ffmpeg_README.txt", 0346 f"{pkg_root}\\bin") 0347 0348 # Copy embedded Python 0349 subprocess.run(["xcopy", "/S", "/Y", "/I", 0350 f"{DEPS_INSTALL_DIR}\\python", f"{pkg_root}\\python"], check=True) 0351 if os.path.exists(f"{pkg_root}\\python\\python.exe"): 0352 os.remove(f"{pkg_root}\\python\\python.exe") 0353 if os.path.exists(f"{pkg_root}\\python\\pythonw.exe"): 0354 os.remove(f"{pkg_root}\\python\\pythonw.exe") 0355 0356 # Remove Python cache files 0357 for d in os.walk(pkg_root): 0358 pycache = f"{d[0]}\\__pycache__" 0359 if os.path.isdir(pycache): 0360 print(f"Deleting Python cache {pycache}") 0361 shutil.rmtree(pycache) 0362 0363 if os.path.exists(f"{pkg_root}\\lib\\site-packages"): 0364 print(f"Deleting unnecessary Python packages") 0365 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\packaging*"): 0366 shutil.rmtree(f) 0367 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\pip*"): 0368 shutil.rmtree(f) 0369 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\ply*"): 0370 shutil.rmtree(f) 0371 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\pyparsing*"): 0372 shutil.rmtree(f) 0373 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\PyQt_builder*"): 0374 shutil.rmtree(f) 0375 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\setuptools.pth"): 0376 os.remove(f) 0377 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\setuptools*"): 0378 shutil.rmtree(f) 0379 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\sip*"): 0380 shutil.rmtree(f) 0381 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\toml*"): 0382 shutil.rmtree(f) 0383 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\easy-install.pth"): 0384 os.remove(f) 0385 0386 if args.pre_zip_hook: 0387 print("Running pre-zip hook...") 0388 subprocess.run(["cmd", "/c", args.pre_zip_hook, 0389 f"{pkg_root}\\"], check=True) 0390 0391 print("\nPackaging stripped binaries...") 0392 subprocess.run([os.environ["SEVENZIP_EXE"], "a", "-tzip", 0393 f"{pkg_name}.zip", f"{pkg_root}\\", "-xr!**\*.pdb"], check=True) 0394 print("--------\n") 0395 print("Packaging debug info...") 0396 # (note that the top-level package dir is not included) 0397 subprocess.run([os.environ["SEVENZIP_EXE"], "a", "-tzip", 0398 f"{pkg_name}-dbg.zip", "-r", f"{pkg_root}\\**\\*.pdb"], check=True) 0399 print("--------\n") 0400 0401 print("\n") 0402 print(f"Krita packaged as {pkg_name}.zip") 0403 if os.path.isfile(f"{pkg_name}-dbg.zip"): 0404 print(f"Debug info packaged as {pkg_name}-dbg.zip") 0405 print(f"Packaging dir is {pkg_root}") 0406 print("NOTE: Do not create installer with packaging dir. Extract from") 0407 print(f" {pkg_name}.zip instead") 0408 print("and do _not_ run krita inside the extracted directory because it will") 0409 print(" create extra unnecessary files.\n") 0410 print("Please remember to actually test the package before releasing it.\n")