File indexing completed on 2024-05-12 16:02:32
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}\\lib", exist_ok=True) 0213 os.makedirs(f"{pkg_root}\\share", exist_ok=True) 0214 except: 0215 warnings.warn("ERROR: Cannot create packaging dir tree!") 0216 exit(1) 0217 0218 print("\nCopying files...") 0219 # krita.exe 0220 shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\krita.exe", f"{pkg_root}\\bin\\") 0221 shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\krita.com", f"{pkg_root}\\bin\\") 0222 shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\krita.pdb", f"{pkg_root}\\bin\\") 0223 # kritarunner.exe 0224 shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\kritarunner.exe", f"{pkg_root}\\bin\\") 0225 shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\kritarunner.pdb", 0226 f"{pkg_root}\\bin\\") 0227 shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\kritarunner_com.com", 0228 f"{pkg_root}\\bin\\") 0229 0230 if os.path.isfile(f"{KRITA_INSTALL_DIR}\\bin\\FreehandStrokeBenchmark.exe"): 0231 shutil.copy(f"{KRITA_INSTALL_DIR}\\bin\\FreehandStrokeBenchmark.exe", f"{pkg_root}\\bin\\") 0232 subprocess.run(["xcopy", "/S", "/Y", "/I", 0233 f"{DEPS_INSTALL_DIR}\\bin\\data\\", f"{pkg_root}\\bin\\data\\"]) 0234 0235 # DLLs from bin/ 0236 print("INFO: Copying all DLLs except Qt5 * from bin/") 0237 files = glob.glob(f"{KRITA_INSTALL_DIR}\\bin\\*.dll") 0238 pdbs = glob.glob(f"{KRITA_INSTALL_DIR}\\bin\\*.pdb") 0239 for f in itertools.chain(files, pdbs): 0240 if not os.path.basename(f).startswith("Qt5"): 0241 shutil.copy(f, f"{pkg_root}\\bin") 0242 files = glob.glob(f"{DEPS_INSTALL_DIR}\\bin\\*.dll") 0243 for f in files: 0244 pdb = f"{os.path.dirname(f)}\\{os.path.splitext(os.path.basename(f))[0]}.pdb" 0245 if not os.path.basename(f).startswith("Qt5"): 0246 shutil.copy(f, f"{pkg_root}\\bin") 0247 if os.path.isfile(pdb): 0248 shutil.copy(pdb, f"{pkg_root}\\bin") 0249 # symsrv.yes for Dr. Mingw 0250 shutil.copy(f"{DEPS_INSTALL_DIR}\\bin\\symsrv.yes", f"{pkg_root}\\bin") 0251 # KF5 plugins may be placed at different locations depending on how Qt is built 0252 subprocess.run(["xcopy", "/S", "/Y", "/I", 0253 f"{DEPS_INSTALL_DIR}\\lib\\plugins\\imageformats\\", f"{pkg_root}\\bin\\imageformats\\"]) 0254 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\plugins\\imageformats\\".format( 0255 DEPS_INSTALL_DIR), f"{pkg_root}\\bin\\imageformats\\"]) 0256 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\plugins\\kf5\\".format( 0257 DEPS_INSTALL_DIR), f"{pkg_root}\\bin\\kf5\\"]) 0258 0259 # Copy the sql drivers explicitly 0260 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\plugins\\sqldrivers\\".format( 0261 DEPS_INSTALL_DIR), f"{pkg_root}\\bin\\sqldrivers"], check=True) 0262 0263 # Qt Translations 0264 # it seems that windeployqt does these, but only * some * of these??? 0265 os.makedirs(f"{pkg_root}\\bin\\translations", exist_ok=True) 0266 files = glob.glob(f"{DEPS_INSTALL_DIR}\\translations\\qt_*.qm") 0267 for f in files: 0268 # Exclude qt_help_*.qm 0269 if not os.path.basename(f).startswith("qt_help"): 0270 shutil.copy(f, f"{pkg_root}\\bin\\translations") 0271 0272 # Krita plugins 0273 subprocess.run(["xcopy", "/Y", "{}\\lib\\kritaplugins\\*.dll".format( 0274 KRITA_INSTALL_DIR), f"{pkg_root}\\lib\\kritaplugins\\"], check=True) 0275 subprocess.run(["xcopy", "/Y", "{}\\lib\\kritaplugins\\*.pdb".format( 0276 KRITA_INSTALL_DIR), f"{pkg_root}\\lib\\kritaplugins\\"], check=True) 0277 if os.path.isdir(f"{DEPS_INSTALL_DIR}\\lib\\krita-python-libs"): 0278 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\lib\\krita-python-libs".format(DEPS_INSTALL_DIR), f"{pkg_root}\\lib\\krita-python-libs"], check=True) 0279 if os.path.isdir(f"{KRITA_INSTALL_DIR}\\lib\\krita-python-libs"): 0280 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\lib\\krita-python-libs".format( 0281 KRITA_INSTALL_DIR), f"{pkg_root}\\lib\\krita-python-libs"], check=True) 0282 if os.path.isdir(f"{DEPS_INSTALL_DIR}\\lib\\site-packages"): 0283 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\lib\\site-packages".format( 0284 DEPS_INSTALL_DIR), f"{pkg_root}\\lib\\site-packages"], check=True) 0285 0286 # Share 0287 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\color".format( 0288 KRITA_INSTALL_DIR), f"{pkg_root}\\share\\color"], check=True) 0289 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\color-schemes".format( 0290 KRITA_INSTALL_DIR), f"{pkg_root}\\share\\color-schemes"], check=True) 0291 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\icons".format( 0292 KRITA_INSTALL_DIR), f"{pkg_root}\\share\\icons"], check=True) 0293 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\krita".format( 0294 KRITA_INSTALL_DIR), f"{pkg_root}\\share\\krita"]) 0295 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\kritaplugins".format( 0296 KRITA_INSTALL_DIR), f"{pkg_root}\\share\\kritaplugins"], check=True) 0297 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\kf5".format( 0298 DEPS_INSTALL_DIR), f"{pkg_root}\\share\\kf5"], check=True) 0299 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\mime".format( 0300 DEPS_INSTALL_DIR), f"{pkg_root}\\share\\mime"], check=True) 0301 # Python libs are copied by share\krita above 0302 # Copy locale to bin 0303 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\locale".format( 0304 KRITA_INSTALL_DIR), f"{pkg_root}\\bin\\locale"]) 0305 subprocess.run(["xcopy", "/S", "/Y", "/I", "{}\\share\\locale".format( 0306 DEPS_INSTALL_DIR), f"{pkg_root}\\bin\\locale"], check=True) 0307 0308 # Copy shortcut link from source (can't create it dynamically) 0309 shutil.copy(f"{KRITA_SRC_DIR}\\packaging\\windows\\krita.lnk", pkg_root) 0310 shutil.copy( 0311 f"{KRITA_SRC_DIR}\\packaging\\windows\\krita-minimal.lnk", pkg_root) 0312 shutil.copy( 0313 f"{KRITA_SRC_DIR}\\packaging\\windows\\krita-animation.lnk", pkg_root) 0314 0315 QMLDIR_ARGS = ["--qmldir", f"{DEPS_INSTALL_DIR}\\qml"] 0316 if os.path.isdir(f"{KRITA_INSTALL_DIR}\\lib\\qml"): 0317 subprocess.run(["xcopy", "/S", "/Y", "/I", 0318 f"{KRITA_INSTALL_DIR}\\lib\\qml", f"{pkg_root}\\bin\\"], check=True) 0319 # This doesn't really seem to do anything 0320 QMLDIR_ARGS.extend(["--qmldir", f"{KRITA_INSTALL_DIR}\\lib\\qml"]) 0321 0322 # windeployqt 0323 subprocess.run(["windeployqt.exe", *QMLDIR_ARGS, "--release", "-gui", "-core", "-concurrent", "-network", "-printsupport", "-svg", 0324 "-xml", "-sql", "-multimedia", "-qml", "-quick", "-quickwidgets", f"{pkg_root}\\bin\\krita.exe", f"{pkg_root}\\bin\\krita.dll"], check=True) 0325 0326 # ffmpeg 0327 if os.path.exists(f"{DEPS_INSTALL_DIR}\\bin\\ffmpeg.exe"): 0328 shutil.copy(f"{DEPS_INSTALL_DIR}\\bin\\ffmpeg.exe", f"{pkg_root}\\bin") 0329 shutil.copy(f"{DEPS_INSTALL_DIR}\\bin\\ffmpeg_LICENSE.txt", 0330 f"{pkg_root}\\bin") 0331 shutil.copy(f"{DEPS_INSTALL_DIR}\\bin\\ffmpeg_README.txt", 0332 f"{pkg_root}\\bin") 0333 0334 # Copy embedded Python 0335 subprocess.run(["xcopy", "/S", "/Y", "/I", 0336 f"{DEPS_INSTALL_DIR}\\python", f"{pkg_root}\\python"], check=True) 0337 if os.path.exists(f"{pkg_root}\\python\\python.exe"): 0338 os.remove(f"{pkg_root}\\python\\python.exe") 0339 if os.path.exists(f"{pkg_root}\\python\\pythonw.exe"): 0340 os.remove(f"{pkg_root}\\python\\pythonw.exe") 0341 0342 # Remove Python cache files 0343 for d in os.walk(pkg_root): 0344 pycache = f"{d[0]}\\__pycache__" 0345 if os.path.isdir(pycache): 0346 print(f"Deleting Python cache {pycache}") 0347 shutil.rmtree(pycache) 0348 0349 if os.path.exists(f"{pkg_root}\\lib\\site-packages"): 0350 print(f"Deleting unnecessary Python packages") 0351 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\packaging*"): 0352 shutil.rmtree(f) 0353 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\pip*"): 0354 shutil.rmtree(f) 0355 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\pyparsing*"): 0356 shutil.rmtree(f) 0357 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\PyQt_builder*"): 0358 shutil.rmtree(f) 0359 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\setuptools*"): 0360 shutil.rmtree(f) 0361 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\sip*"): 0362 shutil.rmtree(f) 0363 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\toml*"): 0364 shutil.rmtree(f) 0365 for f in glob.glob(f"{pkg_root}\\lib\\site-packages\\easy-install.pth"): 0366 os.remove(f) 0367 0368 if args.pre_zip_hook: 0369 print("Running pre-zip hook...") 0370 subprocess.run(["cmd", "/c", args.pre_zip_hook, 0371 f"{pkg_root}\\"], check=True) 0372 0373 print("\nPackaging stripped binaries...") 0374 subprocess.run([os.environ["SEVENZIP_EXE"], "a", "-tzip", 0375 f"{pkg_name}.zip", f"{pkg_root}\\", "-xr!**\*.pdb"], check=True) 0376 print("--------\n") 0377 print("Packaging debug info...") 0378 # (note that the top-level package dir is not included) 0379 subprocess.run([os.environ["SEVENZIP_EXE"], "a", "-tzip", 0380 f"{pkg_name}-dbg.zip", "-r", f"{pkg_root}\\**\\*.pdb"], check=True) 0381 print("--------\n") 0382 0383 print("\n") 0384 print(f"Krita packaged as {pkg_name}.zip") 0385 if os.path.isfile(f"{pkg_name}-dbg.zip"): 0386 print(f"Debug info packaged as {pkg_name}-dbg.zip") 0387 print(f"Packaging dir is {pkg_root}") 0388 print("NOTE: Do not create installer with packaging dir. Extract from") 0389 print(f" {pkg_name}.zip instead") 0390 print("and do _not_ run krita inside the extracted directory because it will") 0391 print(" create extra unnecessary files.\n") 0392 print("Please remember to actually test the package before releasing it.\n")