diff options
| author | karnigen <karnigen@gmail.com> | 2024-01-12 19:01:22 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-01-12 19:01:22 +0100 |
| commit | bc991aaa250339ea2d1ca96c9d531e39d8027ab7 (patch) | |
| tree | b93c665a4468fb6922dc9847ba012c864879b739 /inkstitch.py | |
| parent | 0673df568351fd8ac50a4d2f5b27b91c29b8961d (diff) | |
| parent | 78a3c93fe3b75f9aeb315f13f6ccd925230672c2 (diff) | |
Merge pull request #2653 from inkstitch/kgn/debug_profile_extend_vscode
Kgn/debug profile extend vscode
Diffstat (limited to 'inkstitch.py')
| -rw-r--r-- | inkstitch.py | 187 |
1 files changed, 131 insertions, 56 deletions
diff --git a/inkstitch.py b/inkstitch.py index 91a0f18a..d85eaba4 100644 --- a/inkstitch.py +++ b/inkstitch.py @@ -2,84 +2,159 @@ # # Copyright (c) 2010 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. -import cProfile -import pstats -import logging + import os import sys -from argparse import ArgumentParser -from io import StringIO +from pathlib import Path # to work with paths as objects +import configparser # to read DEBUG.ini + +import lib.debug_utils as debug_utils + +SCRIPTDIR = Path(__file__).parent.absolute() + +running_as_frozen = getattr(sys, 'frozen', None) is not None # check if running from pyinstaller bundle + +if len(sys.argv) < 2: + # no arguments - prevent accidentally running this script + msg = "No arguments given, exiting!" # without gettext localization see _() + if running_as_frozen: # we show dialog only when running from pyinstaller bundle - using wx + try: + import wx + app = wx.App() + dlg = wx.MessageDialog(None, msg, "Inkstitch", wx.OK | wx.ICON_ERROR) + dlg.ShowModal() + dlg.Destroy() + except ImportError: + print(msg) + else: + print(msg) + exit(1) -from lib.exceptions import InkstitchException, format_uncaught_exception +ini = configparser.ConfigParser() +ini.read(SCRIPTDIR / "DEBUG.ini") # read DEBUG.ini file if exists -if getattr(sys, 'frozen', None) is None: - # When running in development mode, we want to use the inkex installed by - # pip install, not the one bundled with Inkscape which is not new enough. - if sys.platform == "darwin": - extensions_path = "/Applications/Inkscape.app/Contents/Resources/share/inkscape/extensions" +# 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_type = 'none' +profiler_type = 'none' + +if not running_as_frozen: # debugging/profiling only in development mode + # specify debugger type + # - if script was already started from debugger then don't read debug type from ini file or cmd line + if not debug_active: + # enable/disable debugger from bash: -d + if os.environ.get('INKSTITCH_DEBUG_ENABLE', '').lower() in ['true', '1', 'yes', 'y']: + debug_enable = True + else: + debug_enable = ini.getboolean("DEBUG","debug_enable", fallback=False) # enable debugger on startup from ini + + debug_type = ini.get("DEBUG","debug_type", fallback="none") # debugger type vscode, pycharm, pydevd + if not debug_enable: + debug_type = 'none' + + debug_to_file = ini.getboolean("DEBUG","debug_to_file", fallback=False) # write debug output to file + if debug_to_file and debug_type == 'none': + debug_type = 'file' + + # enbale/disable profiling from bash: -p + if os.environ.get('INKSTITCH_PROFILE_ENABLE', '').lower() in ['true', '1', 'yes', 'y']: + profile_enable = True else: - extensions_path = "/usr/share/inkscape/extensions" + profile_enable = ini.getboolean("PROFILE","profile_enable", fallback=False) # read from ini + + # specify profiler type + profiler_type = ini.get("PROFILE","profiler_type", fallback="none") # profiler type cprofile, profile, pyinstrument + if not profile_enable: + profiler_type = 'none' + + if running_from_inkscape: + # process creation of the Bash script - should be done before sys.path is modified, see below in prefere_pip_inkex + if ini.getboolean("DEBUG","create_bash_script", fallback=False): # create script only if enabled in DEBUG.ini + debug_utils.write_offline_debug_script(SCRIPTDIR, ini) + + # 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 + + # prefer pip installed inkex over inkscape bundled inkex, pip version is bundled with Inkstitch + # - must be be done before importing inkex + prefere_pip_inkex = ini.getboolean("LIBRARY","prefer_pip_inkex", fallback=True) + if prefere_pip_inkex and 'PYTHONPATH' in os.environ: + debug_utils.reorder_sys_path() + +from argparse import ArgumentParser # to parse arguments and remove --extension +import logging # to set logger for shapely +from io import StringIO # to store shapely errors + +from lib.exceptions import InkstitchException, format_uncaught_exception + +from inkex import errormsg # to show error message in inkscape +from lxml.etree import XMLSyntaxError # to catch XMLSyntaxError from inkex - sys.path.remove(extensions_path) - sys.path.append(extensions_path) +from lib.debug import debug # import global variable debug - don't import whole module -from inkex import errormsg -from lxml.etree import XMLSyntaxError +from lib import extensions # import all supported extensions of institch +from lib.i18n import _ # see gettext translation function _() +from lib.utils import restore_stderr, save_stderr # to hide GTK spam -import lib.debug as debug -from lib import extensions -from lib.i18n import _ -from lib.utils import restore_stderr, save_stderr +# enabling of debug depends on value of debug_type in DEBUG.ini file +if debug_type != 'none': + debug.enable(debug_type, SCRIPTDIR, ini) + # check if debugger is really activated + debug_active = bool((gettrace := getattr(sys, 'gettrace')) and gettrace()) -# ignore warnings in releases -if getattr(sys, 'frozen', None): +# warnings are used by some modules, we want to ignore them all in release +# - see warnings.warn() +if running_as_frozen or not debug_active: import warnings warnings.filterwarnings('ignore') -logger = logging.getLogger('shapely.geos') -logger.setLevel(logging.DEBUG) -shapely_errors = StringIO() -ch = logging.StreamHandler(shapely_errors) -ch.setLevel(logging.DEBUG) -formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s') -ch.setFormatter(formatter) -logger.addHandler(ch) - +# TODO - check if this is still needed for shapely, apparently shapely now uses only exceptions instead of io. +# all logs were removed from version 2.0.0 and above + +# ---- plan to remove this in future ---- +# set logger for shapely - for old versions of shapely +# logger = logging.getLogger('shapely.geos') # attach logger of shapely +# logger.setLevel(logging.DEBUG) +# shapely_errors = StringIO() # in memory file to store shapely errors +# ch = logging.StreamHandler(shapely_errors) +# ch.setLevel(logging.DEBUG) +# formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s') +# ch.setFormatter(formatter) +# logger.addHandler(ch) +# ---- plan to remove this in future ---- + +# pop '--extension' from arguments and generate extension class name from extension name +# example: --extension=params will instantiate Params() class from lib.extensions. parser = ArgumentParser() parser.add_argument("--extension") my_args, remaining_args = parser.parse_known_args() -if os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)), "DEBUG")): - debug.enable() - -profiler = None -if os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)), "PROFILE")): - profiler = cProfile.Profile() - profiler.enable() - extension_name = my_args.extension # example: foo_bar_baz -> FooBarBaz extension_class_name = extension_name.title().replace("_", "") extension_class = getattr(extensions, extension_class_name) -extension = extension_class() +extension = extension_class() # create instance of extension class - call __init__ method -if (hasattr(sys, 'gettrace') and sys.gettrace()) or profiler is not None: - extension.run(args=remaining_args) - if profiler: - path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "profile_stats") - profiler.disable() - profiler.dump_stats(path + ".prof") - - with open(path, 'w') as stats_file: - stats = pstats.Stats(profiler, stream=stats_file) - stats.sort_stats(pstats.SortKey.CUMULATIVE) - stats.print_stats() +# extension run(), we differentiate between debug and normal mode +# - in debug or profile mode we debug or profile extension.run() method +# - in normal mode we run extension.run() in try/except block to catch all exceptions and hide GTK spam +if debug_active or profiler_type != "none": # if debug or profile mode + if profiler_type == 'none': # only debugging + extension.run(args=remaining_args) + else: # do profiling + debug_utils.profile(profiler_type, SCRIPTDIR, ini, extension, remaining_args) - print(f"profiling stats written to {path} and {path}.prof", file=sys.stderr) -else: - save_stderr() +else: # if not debug nor profile mode + save_stderr() # hide GTK spam exception = None try: extension.run(args=remaining_args) @@ -99,7 +174,7 @@ else: finally: restore_stderr() - if shapely_errors.tell(): - errormsg(shapely_errors.getvalue()) + # if shapely_errors.tell(): # see above plan to remove this in future for shapely + # errormsg(shapely_errors.getvalue()) sys.exit(0) |
