From f1f9d275a1ffaeb538a72e3643fb98231323337a Mon Sep 17 00:00:00 2001 From: karnigen Date: Fri, 29 Dec 2023 16:25:17 +0100 Subject: replace DEBUG,PROFILE by DEVEL.ini --- .gitignore | 22 +++++++++-------- DEVEL_template.ini | 35 ++++++++++++++++++++++++++ inkstitch.py | 72 ++++++++++++++++++++++++++++++------------------------ lib/debug.py | 21 +++++++++------- lib/debug_utils.py | 36 +++++++++++++++++++++------ 5 files changed, 127 insertions(+), 59 deletions(-) create mode 100644 DEVEL_template.ini diff --git a/.gitignore b/.gitignore index 2287a056..b84ff90d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,31 +1,33 @@ +__pycache__ *.swp *.kate-swp *.pyc *.spec *.zip *.tar.gz +*.po dist/ build/ locales/ /inx/ -*.po -/DEBUG .pydevproject .project -/debug.log -/debug.svg /.idea +/.vscode /VERSION /src/ .DS_STORE .DS_Store +flaskserverport.json +electron/yarn.lock + +# debug and profile files +/DEVEL.ini +/DEBUG /PROFILE +/debug* +/.debug* +# old profile files /profile_stats /profile_stats.html /profile_stats.prof -/.vscode -__pycache__ -flaskserverport.json -electron/yarn.lock -.ink.sh -.ink.svg diff --git a/DEVEL_template.ini b/DEVEL_template.ini new file mode 100644 index 00000000..1bbc5c1e --- /dev/null +++ b/DEVEL_template.ini @@ -0,0 +1,35 @@ +[LIBRARY] +;;; use the pip installed version of inkex.py, default: True +; prefer_pip_inkex = False + +[DEBUG] +;;; select one active debugger, default: none +; debugger = vscode +; debugger = pycharm +; debugger = pydev + +;;; disable debugger when calling from inkscape, default: False +; disable_from_inkscape = True + +;;; wait for debugger to attach (vscode), default: True +; wait_attach = False + +;;; debug log file, default: debug.log +; debug_file = debug.log + +;;; creation of bash script, default: False +; create_bash_script = True + +;;; base name for bash script, default: debug_inkstitch +; bash_file_base = debug_inkstitch + +[PROFILE] +;;; select one active profiler, default: none +; profiler = cprofile +; profiler = profile +; profiler = pyinstrument + +;;; base name for profile output files, default: debug_profile +; profile_file_base = debug_profile + + diff --git a/inkstitch.py b/inkstitch.py index e0c968b6..bff8e8d1 100644 --- a/inkstitch.py +++ b/inkstitch.py @@ -5,53 +5,60 @@ import os import sys -import lib.debug_utils as debug_utils from pathlib import Path +import configparser +import lib.debug_utils as debug_utils SCRIPTDIR = Path(__file__).parent.absolute() if len(sys.argv) < 2: - exit(1) # no arguments - prevent accidentally running this script + # no arguments - prevent accidentally running this script + print("No arguments given, continue without arguments?") + answer = input("Continue? [y/N] ") + if answer.lower() != 'y': + exit(1) -prefere_pip_inkex = True # prefer pip installed inkex over inkscape bundled inkex +running_as_frozen = getattr(sys, 'frozen', None) is not None # check if running from pyinstaller bundle -# define names of files used by offline Bash script -bash_name = ".ink.sh" -bash_svg = ".ink.svg" +ini = configparser.ConfigParser() +ini.read(SCRIPTDIR / "DEVEL.ini") # read DEVEL.ini file if exists -running_as_frozen = getattr(sys, 'frozen', None) is not None # check if running from pyinstaller bundle -# we assume that if arguments contain svg file (=.ink.svg) then we are running not from inkscape -running_from_inkscape = bash_svg not in sys.argv +# prefer pip installed inkex over inkscape bundled inkex, pip version is bundled with Inkstitch +prefere_pip_inkex = ini.getboolean("LIBRARY","prefer_pip_inkex", fallback=True) + +# check if running from inkscape, given by environment variable +if os.environ.get('INKSTITCH_OFFLINE_SCRIPT', '').lower() in ['true', '1', 'yes', 'y']: + running_from_inkscape = False +else: + running_from_inkscape = True debug_active = bool((gettrace := getattr(sys, 'gettrace')) and gettrace()) # check if debugger is active on startup -debug_file = SCRIPTDIR / "DEBUG" debug_type = 'none' - -profile_file = SCRIPTDIR / "PROFILE" profile_type = 'none' if not running_as_frozen: # debugging/profiling only in development mode - # parse debug file + # define names of files used by offline Bash script + bash_file_base = ini.get("DEBUG","bash_file_base", fallback="debug_inkstitch") + bash_name = Path(bash_file_base).with_suffix(".sh") # Path object + bash_svg = Path(bash_file_base).with_suffix(".svg") # Path object + + # specify debugger type # - if script was already started from debugger then don't read debug file - if not debug_active and os.path.exists(debug_file): - debug_type = debug_utils.parse_file(debug_file) # read type of debugger from debug_file DEBUG - if debug_type == 'none': # for better backward compatibility - print(f"Debug file exists but no debugger type found in '{debug_file.name}'", file=sys.stderr) + if not debug_active: + debug_type = ini.get("DEBUG","debugger", fallback="none") # debugger type vscode, pycharm, pydevd, none - # parse profile file - if os.path.exists(profile_file): - profile_type = debug_utils.parse_file(profile_file) # read type of profiler from profile_file PROFILE - if profile_type == 'none': # for better backward compatibility - print(f"Profile file exists but no profiler type found in '{profile_file.name}'", file=sys.stderr) + # specify profiler type + profile_type = ini.get("PROFILE","profiler", fallback="none") # profiler type cprofile, profile, pyinstrument, none # process creation of the Bash script if running_from_inkscape: - if debug_type.endswith('-script'): # if offline debugging just create script for later debugging + if ini.getboolean("DEBUG","create_bash_script", fallback=False): # create script only if enabled in DEVEL.ini debug_utils.write_offline_debug_script(SCRIPTDIR, bash_name, bash_svg) + + # disable debugger when running from inkscape + disable_from_inkscape = ini.getboolean("DEBUG","disable_from_inkscape", fallback=False) + if disable_from_inkscape: debug_type = 'none' # do not start debugger when running from inkscape - else: # not running from inkscape - if debug_type.endswith('-script'): # remove '-script' to propely initialize debugger packages for each editor - debug_type = debug_type.replace('-script', '') if prefere_pip_inkex and 'PYTHONPATH' in os.environ: # see static void set_extensions_env() in inkscape/src/inkscape-main.cpp @@ -100,7 +107,9 @@ from lib.utils import restore_stderr, save_stderr # file DEBUG exists next to inkstitch.py - enabling debug mode depends on value of debug_type in DEBUG file if debug_type != 'none': - debug.enable(debug_type) + debug_file = ini.get("DEBUG","debug_file", fallback="debug.log") + wait_attach = ini.getboolean("DEBUG","wait_attach", fallback=True) # currently only for vscode + debug.enable(debug_type, debug_file, wait_attach) # check if debugger is really activated debug_active = bool((gettrace := getattr(sys, 'gettrace')) and gettrace()) @@ -136,9 +145,8 @@ extension = extension_class() # create instance of extension class - call __ini # - in debug or profile mode we run extension or profile extension # - in normal mode we run extension in try/except block to catch all exceptions and hide GTK spam if debug_active or profile_type != "none": # if debug or profile mode - print(f"Extension:'{extension_name}' Debug active:{debug_active} type:'{debug_type}' " - f"Profile type:'{profile_type}'", file=sys.stderr) - profile_path = SCRIPTDIR / "profile_stats" + profile_file_base = ini.get("PROFILE","profile_file_base", fallback="debug_profile") + profile_path = SCRIPTDIR / profile_file_base # Path object if profile_type == 'none': extension.run(args=remaining_args) @@ -156,7 +164,7 @@ if debug_active or profile_type != "none": # if debug or profile mode stats = pstats.Stats(profiler, stream=stats_file) stats.sort_stats(pstats.SortKey.CUMULATIVE) stats.print_stats() - print(f"profiling stats written to '{profile_path.name}' and '{profile_path.name}.prof'", file=sys.stderr) + print(f"profiling stats written to '{profile_path.name}' and '{profile_path.name}.prof'. Use snakeviz to see it.", file=sys.stderr) elif profile_type == 'profile': import profile @@ -183,7 +191,7 @@ if debug_active or profile_type != "none": # if debug or profile mode profile_path = SCRIPTDIR / "profile_stats.html" with open(profile_path, 'w') as stats_file: stats_file.write(profiler.output_html()) - print(f"profiling stats written to '{profile_path.name}'", file=sys.stderr) + print(f"profiling stats written to '{profile_path.name}'. Use browser to see it.", file=sys.stderr) else: # if not debug nor profile mode save_stderr() # hide GTK spam diff --git a/lib/debug.py b/lib/debug.py index 6934d6ed..a256bc0a 100644 --- a/lib/debug.py +++ b/lib/debug.py @@ -63,23 +63,25 @@ class Debug(object): def __init__(self): self.debugger = None + self.wait_attach = True self.enabled = False self.last_log_time = None self.current_layer = None self.group_stack = [] - def enable(self, debug_type): + def enable(self, debug_type, debug_file, wait_attach): if debug_type == 'none': return self.debugger = debug_type + self.wait_attach = wait_attach self.enabled = True - self.init_log() + self.init_log(debug_file) self.init_debugger() self.init_svg() - def init_log(self): - self.log_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), "debug.log") + def init_log(self, debug_file): + self.log_file = os.path.join(os.path.dirname(os.path.dirname(__file__)), debug_file) # delete old content with open(self.log_file, "w"): pass @@ -196,9 +198,10 @@ class Debug(object): try: if self.debugger == 'vscode': debugpy.listen(('localhost', 5678)) - print("Waiting for debugger attach") - debugpy.wait_for_client() # wait for debugger to attach - debugpy.breakpoint() # stop here to start normal debugging + if self.wait_attach: + print("Waiting for debugger attach") + debugpy.wait_for_client() # wait for debugger to attach + debugpy.breakpoint() # stop here to start normal debugging elif self.debugger == 'pycharm': pydevd_pycharm.settrace('localhost', port=5678, stdoutToServer=True, stderrToServer=True) @@ -360,5 +363,5 @@ class Debug(object): debug = Debug() -def enable(debug_type): - debug.enable(debug_type) +def enable(debug_type, debug_file, wait_attach): + debug.enable(debug_type, debug_file, wait_attach) diff --git a/lib/debug_utils.py b/lib/debug_utils.py index b5d7aa6a..183a44f8 100644 --- a/lib/debug_utils.py +++ b/lib/debug_utils.py @@ -5,6 +5,12 @@ import os import sys +from pathlib import Path + +# this file is without: import inkex +# - so we can modify sys.path before importing inkex + +# DEBUG and PROFILE are in DEVEL.ini file # DEBUG file format: # - first non-comment line is debugger type @@ -45,12 +51,24 @@ def parse_file(filename): break return value_type -def write_offline_debug_script(SCRIPTDIR, bash_name, bash_svg): +def write_offline_debug_script(SCRIPTDIR : Path, bash_name : Path, bash_svg : Path): # prepare script for offline debugging from console - # - only tested on linux + + # check if input svg file exists in arguments, take argument that not start with '-' as file name + svgs = [arg for arg in sys.argv[1:] if not arg.startswith('-')] + if len(svgs) != 1: + print(f"WARN: {len(svgs)} svg files found, expected 1, [{svgs}]. No script created in write debug script.", file=sys.stderr) + return + + svg_file = Path(svgs[0]) + if svg_file.exists() and bash_svg.exists() and bash_svg.samefile(svg_file): + print(f"WARN: input svg file is same as output svg file. No script created in write debug script.", file=sys.stderr) + return + import shutil - ink_file = os.path.join(SCRIPTDIR, bash_name) - with open(ink_file, 'w') as f: + bash_file = SCRIPTDIR / bash_name + + with open(bash_file, 'w') as f: # "w" text mode, automatic conversion of \n to os.linesep f.write(f"#!/usr/bin/env bash\n\n") f.write(f"# python version: {sys.version}\n") # python version @@ -67,13 +85,11 @@ def write_offline_debug_script(SCRIPTDIR, bash_name, bash_svg): for p in os.environ.get('PYTHONPATH', '').split(os.pathsep): # PYTHONPATH to list f.write(f"# {p}\n") - # take argument that not start with '-' as file name - svg_file = " ".join([arg for arg in sys.argv[1:] if not arg.startswith('-')]) f.write(f"# copy {svg_file} to {bash_svg}\n") # check if files are not the same if svg_file != bash_svg: shutil.copy(svg_file, SCRIPTDIR / bash_svg) # copy file to bash_svg - myargs = myargs.replace(svg_file, bash_svg) # replace file name with bash_svg + myargs = myargs.replace(str(svg_file), str(bash_svg)) # replace file name with bash_svg # see void Extension::set_environment() in inkscape/src/extension/extension.cpp notexported = ["SELF_CALL"] # if an extension calls inkscape itself @@ -86,5 +102,9 @@ def write_offline_debug_script(SCRIPTDIR, bash_name, bash_svg): if k in os.environ: f.write(f'export {k}="{os.environ[k]}"\n') + f.write('# signal inkstitch.py that we are running from offline script\n') + f.write(f'export INKSTITCH_OFFLINE_SCRIPT="True"\n') + + f.write('# call inkstitch\n') f.write(f"python3 inkstitch.py {myargs}\n") - os.chmod(ink_file, 0o0755) # make file executable + bash_file.chmod(0o0755) # make file executable -- cgit v1.2.3