kopia lustrzana https://github.com/inkstitch/inkstitch
184 wiersze
7.5 KiB
Python
184 wiersze
7.5 KiB
Python
# Authors: see git history
|
|
#
|
|
# Copyright (c) 2010 Authors
|
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
|
|
|
import os
|
|
import sys
|
|
from pathlib import Path # to work with paths as objects
|
|
import configparser # to read DEBUG.ini
|
|
|
|
# this file is without: import inkex
|
|
# - we need dump argv and sys.path as is on startup from inkscape
|
|
# - later sys.path may be modified that influences importing inkex (see prefere_pip_inkex)
|
|
|
|
|
|
|
|
def write_offline_debug_script(debug_script_dir : Path, ini : configparser.ConfigParser):
|
|
'''
|
|
prepare Bash script for offline debugging from console
|
|
arguments:
|
|
- debug_script_dir - Path object, absolute path to directory of inkstitch.py
|
|
- ini - see DEBUG.ini
|
|
'''
|
|
|
|
# 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
|
|
|
|
# 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 # to copy svg file
|
|
bash_file = debug_script_dir / 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
|
|
|
|
myargs = " ".join(sys.argv[1:])
|
|
f.write(f'# script: {sys.argv[0]} arguments: {myargs}\n') # script name and arguments
|
|
|
|
# python module path
|
|
f.write(f"# python sys.path:\n")
|
|
for p in sys.path:
|
|
f.write(f"# {p}\n")
|
|
|
|
# see static void set_extensions_env() in inkscape/src/inkscape-main.cpp
|
|
f.write(f"# PYTHONPATH:\n")
|
|
for p in os.environ.get('PYTHONPATH', '').split(os.pathsep): # PYTHONPATH to list
|
|
f.write(f"# {p}\n")
|
|
|
|
f.write(f"# copy {svg_file} to {bash_svg}\n")
|
|
shutil.copy(svg_file, debug_script_dir / bash_svg) # copy file to 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
|
|
exported = ["INKEX_GETTEXT_DOMAIN", "INKEX_GETTEXT_DIRECTORY",
|
|
"INKSCAPE_PROFILE_DIR", "DOCUMENT_PATH", "PYTHONPATH"]
|
|
for k in notexported:
|
|
if k in os.environ:
|
|
f.write(f'# export {k}="{os.environ[k]}"\n')
|
|
for k in exported:
|
|
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")
|
|
bash_file.chmod(0o0755) # make file executable, hopefully ignored on Windows
|
|
|
|
|
|
def reorder_sys_path():
|
|
'''
|
|
change sys.path to prefer pip installed inkex over inkscape bundled inkex
|
|
'''
|
|
|
|
# see static void set_extensions_env() in inkscape/src/inkscape-main.cpp
|
|
# what we do:
|
|
# - move inkscape extensions path to the end of sys.path
|
|
# - we compare PYTHONPATH with sys.path and move PYTHONPATH to the end of sys.path
|
|
# - also user inkscape extensions path is moved to the end of sys.path - may cause problems?
|
|
# - path for deprecated-simple are removed from sys.path, will be added later by importing inkex
|
|
|
|
# PYTHONPATH to list
|
|
pythonpath = os.environ.get('PYTHONPATH', '').split(os.pathsep)
|
|
# remove pythonpath from sys.path
|
|
sys.path = [p for p in sys.path if p not in pythonpath]
|
|
# remove deprecated-simple, it will be added later by importing inkex
|
|
pythonpath = [p for p in pythonpath if not p.endswith('deprecated-simple')]
|
|
# remove nonexisting paths
|
|
pythonpath = [p for p in pythonpath if os.path.exists(p)]
|
|
# add pythonpath to the end of sys.path
|
|
sys.path.extend(pythonpath)
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Profilers:
|
|
# currently supported profilers:
|
|
# - cProfile - standard python profiler
|
|
# - profile - standard python profiler
|
|
# - pyinstrument - profiler with nice html output
|
|
|
|
|
|
def profile(profile_type, profile_dir : Path, ini : configparser.ConfigParser, extension, remaining_args):
|
|
'''
|
|
profile with cProfile, profile or pyinstrument
|
|
'''
|
|
profile_file_base = ini.get("PROFILE","profile_file_base", fallback="debug_profile")
|
|
profile_file_path = profile_dir / profile_file_base # Path object
|
|
|
|
if profile_type == 'cprofile':
|
|
with_cprofile(extension, remaining_args, profile_file_path)
|
|
elif profile_type == 'profile':
|
|
with_profile(extension, remaining_args, profile_file_path)
|
|
elif profile_type == 'pyinstrument':
|
|
with_pyinstrument(extension, remaining_args, profile_file_path)
|
|
else:
|
|
raise ValueError(f"unknown profiler type: '{profile_type}'")
|
|
|
|
def with_cprofile(extension, remaining_args, profile_file_path):
|
|
'''
|
|
profile with cProfile
|
|
'''
|
|
import cProfile
|
|
import pstats
|
|
profiler = cProfile.Profile()
|
|
|
|
profiler.enable()
|
|
extension.run(args=remaining_args)
|
|
profiler.disable()
|
|
|
|
profiler.dump_stats(profile_file_path.with_suffix(".prof")) # can be read by 'snakeviz -s' or 'pyprof2calltree'
|
|
with open(profile_file_path, 'w') as stats_file:
|
|
stats = pstats.Stats(profiler, stream=stats_file)
|
|
stats.sort_stats(pstats.SortKey.CUMULATIVE)
|
|
stats.print_stats()
|
|
print(f"Profiler: cprofile, stats written to '{profile_file_path.name}' and '{profile_file_path.name}.prof'. Use snakeviz to see it.",
|
|
file=sys.stderr)
|
|
|
|
def with_profile(extension, remaining_args, profile_file_path):
|
|
'''
|
|
profile with profile
|
|
'''
|
|
import profile
|
|
import pstats
|
|
profiler = profile.Profile()
|
|
|
|
profiler.run('extension.run(args=remaining_args)')
|
|
|
|
profiler.dump_stats(profile_file_path.with_suffix(".prof")) # can be read by 'snakeviz' or 'pyprof2calltree' - seems broken
|
|
with open(profile_file_path, 'w') as stats_file:
|
|
stats = pstats.Stats(profiler, stream=stats_file)
|
|
stats.sort_stats(pstats.SortKey.CUMULATIVE)
|
|
stats.print_stats()
|
|
print(f"'Profiler: profile, stats written to '{profile_file_path.name}' and '{profile_file_path.name}.prof'. Use of snakeviz is broken.",
|
|
file=sys.stderr)
|
|
|
|
def with_pyinstrument(extension, remaining_args, profile_file_path):
|
|
'''
|
|
profile with pyinstrument
|
|
'''
|
|
import pyinstrument
|
|
profiler = pyinstrument.Profiler()
|
|
|
|
profiler.start()
|
|
extension.run(args=remaining_args)
|
|
profiler.stop()
|
|
|
|
profile_file_path = profile_file_path.with_suffix(".html")
|
|
with open(profile_file_path, 'w') as stats_file:
|
|
stats_file.write(profiler.output_html())
|
|
print(f"Profiler: pyinstrument, stats written to '{profile_file_path.name}'. Use browser to see it.", file=sys.stderr)
|