Unified some errors details

- Non-critical errors will stop when using -W
- More stack traces on error
- More centralized exit
This commit is contained in:
Salvador E. Tropea 2024-01-04 13:58:13 -03:00
parent 6e914b0959
commit 7ec3a1379c
36 changed files with 113 additions and 166 deletions

View File

@ -226,11 +226,9 @@ def solve_config(a_plot_config, quiet=False):
logger.warning(W_VARCFG + 'More than one config file found in current directory.\n'
' Using '+plot_config+' if you want to use another use -c option.')
else:
logger.error('No config file found (*.kibot.yaml), use -c to specify one.')
sys.exit(EXIT_BAD_ARGS)
GS.exit_with_error('No config file found (*.kibot.yaml), use -c to specify one.', EXIT_BAD_ARGS)
if not os.path.isfile(plot_config):
logger.error("Plot config file not found: "+plot_config)
sys.exit(EXIT_BAD_ARGS)
GS.exit_with_error("Plot config file not found: "+plot_config, EXIT_BAD_ARGS)
logger.debug('Using configuration file: `{}`'.format(plot_config))
return plot_config
@ -254,11 +252,10 @@ def detect_kicad():
try:
import pcbnew
except ImportError:
logger.error("Failed to import pcbnew Python module."
" Is KiCad installed?"
" Do you need to add it to PYTHONPATH?")
logger.error(TRY_INSTALL_CHECK)
sys.exit(NO_PCBNEW_MODULE)
GS.exit_with_error(["Failed to import pcbnew Python module."
" Is KiCad installed?"
" Do you need to add it to PYTHONPATH?",
TRY_INSTALL_CHECK], NO_PCBNEW_MODULE)
try:
GS.kicad_version = pcbnew.GetBuildVersion()
except AttributeError:
@ -274,8 +271,7 @@ def detect_kicad():
m = re.search(r'(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?', GS.kicad_version)
if m is None:
logger.error("Unable to detect KiCad version, got: `{}`".format(GS.kicad_version))
sys.exit(NO_PCBNEW_MODULE)
GS.exit_with_error(f"Unable to detect KiCad version, got: `{GS.kicad_version}`", NO_PCBNEW_MODULE)
GS.kicad_version_major = int(m.group(1))
GS.kicad_version_minor = int(m.group(2))
GS.kicad_version_patch = int(m.group(3))
@ -346,8 +342,7 @@ def detect_kicad():
def parse_defines(args):
for define in args.define:
if '=' not in define:
logger.error('Malformed `define` option, must be VARIABLE=VALUE ({})'.format(define))
sys.exit(EXIT_BAD_ARGS)
GS.exit_with_error(f'Malformed `define` option, must be VARIABLE=VALUE ({define})', EXIT_BAD_ARGS)
var = define.split('=')[0]
GS.cli_defines[var] = define[len(var)+1:]
@ -355,8 +350,7 @@ def parse_defines(args):
def parse_global_redef(args):
for redef in args.global_redef:
if '=' not in redef:
logger.error('Malformed global-redef option, must be VARIABLE=VALUE ({})'.format(redef))
sys.exit(EXIT_BAD_ARGS)
GS.exit_with_error(f'Malformed global-redef option, must be VARIABLE=VALUE ({redef})', EXIT_BAD_ARGS)
var = redef.split('=')[0]
GS.cli_global_defs[var] = redef[len(var)+1:]
@ -373,8 +367,7 @@ def apply_warning_filter(args):
try:
log.set_filters([SimpleFilter(int(n)) for n in args.no_warn.split(',')])
except ValueError:
logger.error('-w/--no-warn must specify a comma separated list of numbers ({})'.format(args.no_warn))
sys.exit(EXIT_BAD_ARGS)
GS.exit_with_error(f'-w/--no-warn must specify a comma separated list of numbers ({args.no_warn})', EXIT_BAD_ARGS)
def debug_arguments(args):
@ -437,8 +430,7 @@ def main():
try:
id = int(args.banner)
except ValueError:
logger.error('The banner option needs an integer ({})'.format(id))
sys.exit(EXIT_BAD_ARGS)
GS.exit_with_error(f'The banner option needs an integer ({id})', EXIT_BAD_ARGS)
logger.info(get_banner(id))
if args.help_outputs or args.help_list_outputs:
@ -479,8 +471,7 @@ def main():
if args.example:
check_board_file(args.board_file)
if args.copy_options and not args.board_file:
logger.error('Asked to copy options but no PCB specified.')
sys.exit(EXIT_BAD_ARGS)
GS.exit_with_error('Asked to copy options but no PCB specified.', EXIT_BAD_ARGS)
create_example(args.board_file, GS.out_dir, args.copy_options, args.copy_and_expand)
sys.exit(0)
if args.quick_start:

View File

@ -18,6 +18,7 @@ from .csv_writer import write_csv
from .html_writer import write_html
from .xml_writer import write_xml
from .. import log
from .. import error
logger = log.get_logger()
@ -49,6 +50,6 @@ def write_bom(filename, ext, groups, headings, cfg):
if result:
logger.debug("{} Output -> {}".format(ext.upper(), filename))
else:
logger.error("writing {} output".format(ext.upper()))
raise error.KiPlotError(f"Fail writing {ext.upper()} output")
return result

View File

@ -130,7 +130,7 @@ def get_prefix_simple(prefix):
# Unknown, we shouldn't get here because the regex matched
# BUT: I found that sometimes unexpected things happen, like mu matching micro and then we reaching this code
# Now is fixed, but I can't be sure some bizarre case is overlooked
logger.error('Unknown prefix, please report')
logger.non_critical_error('Unknown prefix, please report')
return 0

View File

@ -722,7 +722,7 @@ def write_xlsx(filename, groups, col_fields, head_names, cfg):
cfg = BoMOptions object with all the configuration
"""
if not XLSX_SUPPORT:
logger.error('Python xlsxwriter module not installed')
logger.non_critical_error('Python xlsxwriter module not installed')
return False
link_datasheet = -1

View File

@ -17,7 +17,7 @@ import io
import json
import os
import re
from sys import exit, maxsize
from sys import maxsize
import sys
import textwrap
@ -58,9 +58,7 @@ try:
import yaml
except ImportError:
log.init()
logger.error('No yaml module for Python, install python3-yaml')
logger.error(TRY_INSTALL_CHECK)
exit(NO_YAML_MODULE)
GS.exit_with_error(['No yaml module for Python, install python3-yaml', TRY_INSTALL_CHECK], NO_YAML_MODULE)
def update_dict(d, u):
@ -650,7 +648,7 @@ class CfgYamlReader(object):
for k, v in collected_definitions[-1].items():
content, replaced = do_replace(k, v, content, replaced)
if depth >= 20:
logger.error('Maximum depth of definition replacements reached, loop?')
logger.non_critical_error('Maximum depth of definition replacements reached, loop?')
if GS.debug_level > 3:
logger.debug('YAML after expanding definitions:\n'+content)
# Create an stream from the string
@ -817,7 +815,7 @@ def print_output_options(name, cl, indent, context=None, skip_keys=False):
entry = ind_base_sp+entry
if help is None:
help = 'Undocumented'
logger.error('Undocumented option: `{}`'.format(k))
logger.non_critical_error(f'Undocumented option: `{k}`')
lines = help.split('\n')
preface = ind_str+entry.format(k)
if rst_mode and context:
@ -944,8 +942,7 @@ def print_outputs_help(rst, details=False):
def print_output_help(name):
if not RegOutput.is_registered(name):
logger.error('Unknown output type `{}`, try --help-list-outputs'.format(name))
exit(EXIT_BAD_ARGS)
GS.exit_with_error(f'Unknown output type `{name}`, try --help-list-outputs', EXIT_BAD_ARGS)
print_one_out_help(True, name, RegOutput.get_class_for(name))
@ -1150,8 +1147,7 @@ def create_example(pcb_file, out_dir, copy_options, copy_expand):
os.makedirs(out_dir)
fname = os.path.join(out_dir, EXAMPLE_CFG)
if os.path.isfile(fname):
logger.error(fname+" already exists, won't overwrite")
exit(WONT_OVERWRITE)
GS.exit_with_error(fname+" already exists, won't overwrite", WONT_OVERWRITE)
with open(fname, 'w') as f:
logger.info('Creating {} example configuration'.format(fname))
f.write("# ATTENTION! THIS ISN'T A FULLY FUNCTIONAL EXAMPLE.\n")

View File

@ -377,7 +377,7 @@ def python_downloader(dep):
# Check if we have pip and wheel
pip_command = check_pip()
if pip_command is None:
logger.error('No pip command available!')
logger.non_critical_error('No pip command available!')
return False
# Try to pip install it
if not pip_install(pip_command, name=dep.pypi_name.lower()):
@ -737,7 +737,7 @@ def try_download_tool_binary(dep):
if res:
using_downloaded(dep)
except Exception as e:
logger.error('- Failed to download {}: {}'.format(dep.name, e))
logger.non_critical_error(f'- Failed to download {dep.name}: {e}')
return res, ver
@ -799,14 +799,13 @@ def check_tool_python(dep, reload=False):
importlib.invalidate_caches()
mod = importlib.import_module(dep.module_name)
if mod.__file__ is None:
logger.error(mod)
return None, None
res, ver = check_tool_python_version(mod, dep)
if res is not None and reload:
res = importlib.reload(reload)
return res, ver
except ModuleNotFoundError:
logger.error(f'Pip failed for {dep.module_name}')
logger.non_critical_error(f'Pip failed for {dep.module_name}')
return None, None
@ -970,7 +969,7 @@ def register_dep(context, dep):
if parent:
parent_data = base_deps.get(parent.lower(), None)
if parent_data is None:
logger.error('{} dependency unkwnown parent {}'.format(context, parent))
logger.non_critical_error(f'{context} dependency unkwnown parent {parent}')
return
new_dep = deepcopy(parent_data)
new_dep.update(dep)

View File

@ -208,7 +208,7 @@ class Spec_to_Field(BaseFilter): # noqa: F821
v = self.normalize(v, EI_TYPES[n], c)
if not self.compare(cur_val, v):
desc = "`{}` vs `{}` collision, `{}` != `{}`".format(d, cur_dist, v, cur_val)
logger.error(desc)
logger.non_critical_error(desc)
def filter(self, comp):
self.solve_from()

View File

@ -135,7 +135,7 @@ class Subparts(BaseFilter): # noqa: F821
return float(vals[0])/float(vals[1])
return float(qty)
except ValueError:
logger.error('Internal error qty_to_float("{}"), please report'.format(qty))
logger.non_critical_error(f'Internal error qty_to_float("{qty}"), please report')
def do_split(self, comp, max_num_subparts, split_fields):
""" Split `comp` according to the detected subparts """

View File

@ -72,10 +72,10 @@ def parse_len_str(val):
c = int(val[:pos])
except ValueError:
c = None
logger.error('Malformed 3D alias entry: '+val)
logger.non_critical_error('Malformed 3D alias entry: '+val)
value = val[pos+1:]
if c is not None and c != len(value):
logger.error('3D alias entry error, expected len {}, but found {}'.format(c, len(value)))
logger.non_critical_error(f'3D alias entry error, expected len {c}, but found {len(value)}')
return value
@ -110,7 +110,7 @@ def expand_env(val, env, extra_env, used_extra=None):
success = False
# Note: We can't expand NET_NAME(n)
if var not in reported and not var.startswith('NET_NAME('):
logger.error('Unable to expand `{}` in `{}`'.format(var, val))
logger.non_critical_error(f'Unable to expand `{var}` in `{val}`')
reported.add(var)
return val
@ -585,7 +585,7 @@ class KiConf(object):
logger.warning(W_3DRESVER, 'Unsupported 3D resolver version ({})'.format(head))
for r in reader:
if len(r) != 3:
logger.error("3D resolver doesn't contain three values ({})".format(r))
logger.non_critical_error(f"3D resolver doesn't contain three values ({r})")
continue
name = parse_len_str(r[0])
value = parse_len_str(r[1])
@ -607,8 +607,7 @@ class KiConf(object):
data[key]['page_layout_descr_file'] = key+'.kicad_wks'
return dest
else:
logger.error('Missing page layout file: '+fname)
exit(MISSING_WKS)
GS.exit_with_error('Missing page layout file: '+fname, MISSING_WKS)
return None
def fix_page_layout_k6(project, dry):
@ -658,8 +657,7 @@ class KiConf(object):
dest = str(order)+'.kicad_wks'
order = order+1
else:
logger.error('Missing page layout file: '+fname)
exit(MISSING_WKS)
GS.exit_with_error('Missing page layout file: '+fname, MISSING_WKS)
else:
dest = ''
lns[c] = f'PageLayoutDescrFile={dest}\n'

View File

@ -73,14 +73,14 @@ class DCMLineReader(LineReader):
try:
res = res.decode()
except UnicodeDecodeError:
logger.error('Invalid UTF-8 sequence at line {} of file `{}`'.format(self.line+1, self.file))
logger.non_critical_error(f'Invalid UTF-8 sequence at line {self.line+1} of file `{self.file}`')
nres = ''
for c in res:
if c > 127:
c = 32
nres += chr(c)
res = nres
logger.error('Using: '+res.rstrip())
logger.non_critical_error('Using: '+res.rstrip())
return res
def get_line(self):

View File

@ -26,7 +26,7 @@ from .misc import (PLOT_ERROR, CORRUPTED_PCB, EXIT_BAD_ARGS, CORRUPTED_SCH, vers
MOD_VIRTUAL, W_PCBNOSCH, W_NONEEDSKIP, W_WRONGCHAR, name2make, W_TIMEOUT, W_KIAUTO, W_VARSCH,
NO_SCH_FILE, NO_PCB_FILE, W_VARPCB, NO_YAML_MODULE, WRONG_ARGUMENTS, FAILED_EXECUTE, W_VALMISMATCH,
MOD_EXCLUDE_FROM_POS_FILES, MOD_EXCLUDE_FROM_BOM, MOD_BOARD_ONLY, hide_stderr, W_MAXDEPTH)
from .error import PlotError, KiPlotConfigurationError, config_error
from .error import PlotError, KiPlotConfigurationError, config_error, KiPlotError
from .config_reader import CfgYamlReader
from .pre_base import BasePreFlight
from .dep_downloader import register_deps
@ -46,9 +46,7 @@ try:
import yaml
except ImportError:
log.init()
logger.error('No yaml module for Python, install python3-yaml')
logger.error(TRY_INSTALL_CHECK)
exit(NO_YAML_MODULE)
GS.exit_with_error(['No yaml module for Python, install python3-yaml', TRY_INSTALL_CHECK], NO_YAML_MODULE)
def cased_path(path):
@ -61,8 +59,7 @@ def try_register_deps(mod, name):
try:
data = yaml.safe_load(mod.__doc__)
except yaml.YAMLError as e:
logger.error('While loading plug-in `{}`:'.format(name))
config_error("Error loading YAML "+str(e))
config_error([f'While loading plug-in `{name}`:', "Error loading YAML "+str(e)])
register_deps(name, data)
@ -195,8 +192,7 @@ def exec_with_retry(cmd, exit_with=None):
logger.warning(W_TIMEOUT+'Time out detected, on slow machines or complex projects try:')
logger.warning(W_TIMEOUT+'`kiauto_time_out_scale` and/or `kiauto_wait_start` global options')
if exit_with is not None and ret:
logger.error(cmd[0]+' returned %d', ret)
exit(exit_with)
GS.exit_with_error(cmd[0]+' returned '+str(ret), exit_with)
return ret
@ -232,9 +228,7 @@ def load_board(pcb_file=None, forced=False):
dr.Update()
GS.board = board
except OSError as e:
logger.error('Error loading PCB file. Corrupted?')
logger.error(e)
exit(CORRUPTED_PCB)
GS.exit_with_error(['Error loading PCB file. Corrupted?', str(e)], CORRUPTED_PCB)
assert board is not None
logger.debug("Board loaded")
return board
@ -425,13 +419,11 @@ def preflight_checks(skip_pre, targets):
skip_list = skip_pre.split(',')
for skip in skip_list:
if skip == 'all':
logger.error('All can\'t be part of a list of actions '
'to skip. Use `--skip all`')
exit(EXIT_BAD_ARGS)
GS.exit_with_error('All can\'t be part of a list of actions '
'to skip. Use `--skip all`', EXIT_BAD_ARGS)
else:
if not BasePreFlight.is_registered(skip):
logger.error('Unknown preflight `{}`'.format(skip))
exit(EXIT_BAD_ARGS)
GS.exit_with_error(f'Unknown preflight `{skip}`', EXIT_BAD_ARGS)
o_pre = BasePreFlight.get_preflight(skip)
if not o_pre:
logger.warning(W_NONEEDSKIP + '`{}` preflight is not in use, no need to skip'.format(skip))
@ -480,8 +472,7 @@ def config_output(out, dry=False, dont_stop=False):
def get_output_targets(output, parent):
out = RegOutput.get_output(output)
if out is None:
logger.error('Unknown output `{}` selected in {}'.format(output, parent))
exit(WRONG_ARGUMENTS)
GS.exit_with_error(f'Unknown output `{output}` selected in {parent}', WRONG_ARGUMENTS)
config_output(out)
out_dir = get_output_dir(out.dir, out, dry=True)
files_list = out.get_targets(out_dir)
@ -501,7 +492,7 @@ def run_output(out, dont_stop=False):
try:
out.run(get_output_dir(out.dir, out))
out._done = True
except PlotError as e:
except (PlotError, KiPlotError) as e:
logger.error("In output `"+str(out)+"`: "+str(e))
if not dont_stop:
exit(PLOT_ERROR)
@ -551,12 +542,10 @@ def _generate_outputs(outputs, targets, invert, skip_pre, cli_order, no_priority
# Check we got a valid list of outputs
unknown = next(filter(lambda x: not RegOutput.is_output_or_group(x), targets), None)
if unknown:
logger.error('Unknown output/group `{}`'.format(unknown))
exit(EXIT_BAD_ARGS)
GS.exit_with_error(f'Unknown output/group `{unknown}`', EXIT_BAD_ARGS)
# Check for CLI+invert inconsistency
if cli_order and invert:
logger.error("CLI order and invert options can't be used simultaneously")
exit(EXIT_BAD_ARGS)
GS.exit_with_error("CLI order and invert options can't be used simultaneously", EXIT_BAD_ARGS)
# Expand groups
logger.debug('Outputs before groups expansion: {}'.format(targets))
try:
@ -777,10 +766,9 @@ def guess_ki6_sch(schematics):
def avoid_mixing_5_and_6(sch, kicad_sch):
logger.error('Found KiCad 5 and KiCad 6+ files, make sure the whole project uses one version')
logger.error('KiCad 5: '+os.path.basename(sch))
logger.error('KiCad 6+: '+os.path.basename(kicad_sch))
exit(EXIT_BAD_CONFIG)
GS.exit_with_error(['Found KiCad 5 and KiCad 6+ files, make sure the whole project uses one version',
'KiCad 5: '+os.path.basename(sch),
'KiCad 6+: '+os.path.basename(kicad_sch)], EXIT_BAD_CONFIG)
def solve_schematic(base_dir, a_schematic=None, a_board_file=None, config=None, sug_e=True):
@ -846,8 +834,7 @@ def solve_schematic(base_dir, a_schematic=None, a_board_file=None, config=None,
logger.warning(W_VARSCH + 'More than one SCH file found in `'+base_dir+'`.\n'
' Using '+schematic+msg+'.')
if schematic and not os.path.isfile(schematic):
logger.error("Schematic file not found: "+schematic)
exit(NO_SCH_FILE)
GS.exit_with_error("Schematic file not found: "+schematic, NO_SCH_FILE)
if schematic:
schematic = os.path.abspath(schematic)
logger.debug('Using schematic: `{}`'.format(schematic))
@ -859,8 +846,7 @@ def solve_schematic(base_dir, a_schematic=None, a_board_file=None, config=None,
def check_board_file(board_file):
if board_file and not os.path.isfile(board_file):
logger.error("Board file not found: "+board_file)
exit(NO_PCB_FILE)
GS.exit_with_error("Board file not found: "+board_file, NO_PCB_FILE)
def solve_board_file(base_dir, a_board_file=None, sug_b=True):
@ -1120,8 +1106,7 @@ def generate_examples(start_dir, dry, types):
start_dir = '.'
else:
if not os.path.isdir(start_dir):
logger.error('Invalid dir {} to quick start'.format(start_dir))
exit(WRONG_ARGUMENTS)
GS.exit_with_error(f'Invalid dir {start_dir} to quick start', WRONG_ARGUMENTS)
# Set default global options
glb = GS.class_for_global_opts()
glb.set_tree({})

View File

@ -108,6 +108,9 @@ class MyLogger(logging.Logger):
super().warning(buf, stacklevel=2, **kwargs) # pragma: no cover (Py38)
else:
super().warning(buf, **kwargs)
self.check_warn_stop()
def check_warn_stop(self):
if stop_on_warnings:
self.error('Warnings treated as errors')
sys.exit(WARN_AS_ERROR)
@ -148,6 +151,10 @@ class MyLogger(logging.Logger):
filt_msg = ', {} filtered'.format(MyLogger.n_filtered)
self.info('Found {} unique warning/s ({} total{})'.format(MyLogger.warn_cnt, MyLogger.warn_tcnt, filt_msg))
def non_critical_error(self, msg):
self.error(msg)
self.check_warn_stop()
def findCaller(self, stack_info=False, stacklevel=1):
f = sys._getframe(1)
# Skip frames from logging module

View File

@ -293,6 +293,7 @@ W_VALMISMATCH = '(W135) '
W_BADOFFSET = '(W136) '
W_BUG16418 = '(W137) '
W_NOTHCMP = '(W138) '
W_KEEPTMP = '(W139) '
# Somehow arbitrary, the colors are real, but can be different
PCB_MAT_COLORS = {'fr1': "937042", 'fr2': "949d70", 'fr3': "adacb4", 'fr4': "332B16", 'fr5': "6cc290"}
PCB_FINISH_COLORS = {'hal': "8b898c", 'hasl': "8b898c", 'imag': "8b898c", 'enig': "cfb96e", 'enepig': "cfb96e",

View File

@ -151,9 +151,8 @@ class AnyLayerOptions(VariantOptions):
def run(self, output_dir, layers):
super().run(output_dir)
if GS.ki7 and GS.kicad_version_n < KICAD_VERSION_7_0_1 and not self.exclude_edge_layer:
logger.error("Plotting the edge layer is not supported by KiCad 7.0.0\n"
"Please upgrade KiCad to 7.0.1 or newer")
exit(MISSING_TOOL)
GS.exit_with_error("Plotting the edge layer is not supported by KiCad 7.0.0\n"
"Please upgrade KiCad to 7.0.1 or newer", MISSING_TOOL)
# Memorize the list of visible layers
old_visible = GS.board.GetVisibleLayers()
# Apply the variants and filters

View File

@ -70,9 +70,8 @@ class Any_PCB_PrintOptions(VariantOptions):
def run(self, output, svg=False):
super().run(self._layers)
if GS.ki7 and GS.kicad_version_n < KICAD_VERSION_7_0_1 and self.scaling != 0 and self.scaling != 1.0:
logger.error("Scaled printing is broken in KiCad 7.0.0\n"
"Please upgrade KiCad to 7.0.1 or newer")
exit(MISSING_TOOL)
GS.exit_with_error("Scaled printing is broken in KiCad 7.0.0\n"
"Please upgrade KiCad to 7.0.1 or newer", MISSING_TOOL)
command = self.ensure_tool('KiAuto')
# Output file name
cmd = [command, 'export', '--output_name', output]

View File

@ -11,7 +11,7 @@ from shutil import rmtree
from tempfile import NamedTemporaryFile, mkdtemp
from .gs import GS
from .kiplot import load_sch, get_board_comps_data
from .misc import Rect, W_WRONGPASTE, DISABLE_3D_MODEL_TEXT, W_NOCRTYD, MOD_ALLOW_MISSING_COURTYARD, W_MISSDIR
from .misc import Rect, W_WRONGPASTE, DISABLE_3D_MODEL_TEXT, W_NOCRTYD, MOD_ALLOW_MISSING_COURTYARD, W_MISSDIR, W_KEEPTMP
if not GS.kicad_version_n:
# When running the regression tests we need it
from kibot.__main__ import detect_kicad
@ -113,7 +113,7 @@ class BaseOutput(RegOutput):
def get_targets(self, out_dir):
""" Returns a list of targets generated by this output """
if not (hasattr(self, "options") and hasattr(self.options, "get_targets")):
logger.error("Output {} doesn't implement get_targets(), please report it".format(self))
logger.non_critical_error(f"Output {self} doesn't implement get_targets(), please report it")
return []
return self.options.get_targets(out_dir)
@ -1006,7 +1006,7 @@ class VariantOptions(BaseOptions):
except SystemExit:
if GS.debug_enabled:
if self._files_to_remove:
logger.error('Keeping temporal files: '+str(self._files_to_remove))
logger.warning(W_KEEPTMP+'Keeping temporal files: '+str(self._files_to_remove))
else:
self.remove_temporals()
raise

View File

@ -236,7 +236,7 @@ class Base3DOptions(VariantOptions):
try:
replace = download_easyeda_3d_model(lcsc_id, self._tmp_dir, fname)
except Exception as e:
logger.error(f'Error downloading 3D model for LCSC part {lcsc_id} (model: {model} problem: {e})')
logger.non_critical_error(f'Error downloading 3D model for LCSC part {lcsc_id} (model: {model} problem: {e})')
replace = None
if not replace:
return None

View File

@ -14,7 +14,6 @@ Dependencies:
import json
import os
import re
import sys
from tempfile import NamedTemporaryFile, TemporaryDirectory
from .error import KiPlotConfigurationError
from .kiplot import get_output_targets, run_output, run_command, register_xmp_import, config_output, configure_and_run
@ -551,13 +550,11 @@ class Blender_ExportOptions(BaseOptions):
def analyze_errors(self, msg):
if 'Traceback ' in msg:
logger.error('Error from Blender run:\n'+msg[msg.index('Traceback '):])
sys.exit(BLENDER_ERROR)
GS.exit_with_error('Error from Blender run:\n'+msg[msg.index('Traceback '):], BLENDER_ERROR)
def run(self, output):
if GS.ki5:
logger.error("`blender_export` needs KiCad 6+")
exit(MISSING_TOOL)
GS.exit_with_error("`blender_export` needs KiCad 6+", MISSING_TOOL)
pcb3d_file = self.solve_pcb3d()
# If no outputs specified just finish
# Can be used to export the PCB to Blender

View File

@ -172,8 +172,7 @@ class CompressOptions(BaseOptions):
run_output(out)
if not os.path.exists(file):
# Still missing, something is wrong
logger.error('Unable to generate `{}` from {}'.format(file, out))
exit(INTERNAL_ERROR)
GS.exit_with_error(f'Unable to generate `{file}` from {out}', INTERNAL_ERROR)
if os.path.isdir(file):
# Popultate output adds the image dirs
# Computing its content is complex:

View File

@ -9,7 +9,6 @@ import glob
import os
import re
from shutil import copy2
from sys import exit
from .error import KiPlotConfigurationError
from .gs import GS
from .kiplot import config_output, get_output_dir, run_output
@ -106,8 +105,7 @@ class Copy_FilesOptions(Base3DOptions):
files_list = out.get_targets(out_dir)
logger.debugl(2, '- List of files: {}'.format(files_list))
else:
logger.error('Unknown output `{}` selected in {}'.format(from_output, self._parent))
exit(WRONG_ARGUMENTS)
GS.exit_with_error(f'Unknown output `{from_output}` selected in {self._parent}', WRONG_ARGUMENTS)
# Check if we must run the output to create the files
if not no_out_run:
for file in files_list:
@ -118,8 +116,7 @@ class Copy_FilesOptions(Base3DOptions):
run_output(out)
if not os.path.isfile(file):
# Still missing, something is wrong
logger.error('Unable to generate `{}` from {}'.format(file, out))
exit(INTERNAL_ERROR)
GS.exit_with_error(f'Unable to generate `{file}` from {out}', INTERNAL_ERROR)
return files_list
def copy_footprints(self, dest, dry):

View File

@ -542,8 +542,7 @@ class DiffOptions(VariantOptions):
run_command(cmd, just_raise=True)
except CalledProcessError as e:
if e.returncode == 10:
logger.error('Diff above the threshold')
exit(DIFF_TOO_BIG)
GS.exit_with_error('Diff above the threshold', DIFF_TOO_BIG)
logger.error('Running {} returned {}'.format(e.cmd, e.returncode))
if e.stdout:
logger.debug('- Output from command: '+e.stdout.decode())

View File

@ -166,9 +166,8 @@ class KiCostOptions(VariantOptions):
# Currently we only support the XML mechanism.
netlist = GS.sch_no_ext+'.xml'
if not isfile(netlist):
logger.error('Missing netlist in XML format `{}`'.format(netlist))
logger.error('You can generate it using the `update_xml` preflight')
exit(BOM_ERROR)
GS.exit_with_error([f'Missing netlist in XML format `{netlist}`',
'You can generate it using the `update_xml` preflight'], BOM_ERROR)
# Check KiCost is available
cmd_kicost = abspath(join(dirname(__file__), KICOST_SUBMODULE))
if not isfile(cmd_kicost):

View File

@ -147,9 +147,9 @@ def _run_command(cmd):
try:
cmd_output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
logger.error('Failed to run %s, error %d', cmd[0], e.returncode)
if e.output:
logger.debug('Output from command: '+e.output.decode())
logger.non_critical_error(f'Failed to run {cmd[0]}, error {e.returncode}')
return False
if cmd_output.strip():
logger.debug('- Output from command:\n'+cmd_output.decode())
@ -173,7 +173,7 @@ class Navigate_ResultsOptions(BaseOptions):
self.link_from_root = ''
""" *The name of a file to create at the main output directory linking to the home page """
self.skip_not_run = False
""" Skip outputs with `run_by_default: false` """
# """ Skip outputs with `run_by_default: false` """
super().__init__()
self._expand_id = 'navigate'
self._expand_ext = 'html'

View File

@ -18,7 +18,7 @@ from .error import KiPlotConfigurationError
from .gs import GS
from .kiplot import run_command, config_output, register_xmp_import
from .layer import Layer
from .misc import W_PANELEMPTY, KIKIT_UNIT_ALIASES
from .misc import W_PANELEMPTY, KIKIT_UNIT_ALIASES, W_KEEPTMP
from .optionable import PanelOptions
from .out_base import VariantOptions
from .registrable import RegOutput
@ -723,7 +723,7 @@ class PanelizeOptions(VariantOptions):
finally:
if GS.debug_enabled and not remove_tmps:
if self._files_to_remove:
logger.error('Keeping temporal files: '+str(self._files_to_remove))
logger.warning(W_KEEPTMP+'Keeping temporal files: '+str(self._files_to_remove))
else:
self.remove_temporals()

View File

@ -315,8 +315,7 @@ class PCB2Blender_ToolsOptions(VariantOptions):
def run(self, output):
super().run(output)
if GS.ki5:
logger.error("`pcb2blender_tools` needs KiCad 6+")
exit(MISSING_TOOL)
GS.exit_with_error("`pcb2blender_tools` needs KiCad 6+", MISSING_TOOL)
dir_name = os.path.dirname(output)
self.apply_show_components()
self.filter_pcb_components(do_3D=True)

View File

@ -879,8 +879,7 @@ class PCB_PrintOptions(VariantOptions):
plotter.svg_precision = self.svg_precision
image = plotter.plot()
except (RuntimeError, SyntaxError, IOError) as e:
logger.error('PcbDraw error: '+str(e))
exit(PCBDRAW_ERR)
GS.exit_with_error('PcbDraw error: '+str(e), PCBDRAW_ERR)
if GS.debug_level > 1:
# Save the SVG only for debug purposes

View File

@ -496,8 +496,7 @@ class PcbDrawOptions(VariantOptions):
# When the PCB can't be loaded we get IOError
# When the SVG contains errors we get SyntaxError
except (RuntimeError, SyntaxError, IOError) as e:
logger.error('PcbDraw error: '+str(e))
exit(PCBDRAW_ERR)
GS.exit_with_error('PcbDraw error: '+str(e), PCBDRAW_ERR)
# Save the result
logger.debug('Saving output to '+svg_save_output_name)

View File

@ -70,8 +70,7 @@ class PDFUniteOptions(BaseOptions):
config_output(out)
files_list = out.get_targets(get_output_dir(out.dir, out, dry=True))
else:
logger.error('Unknown output `{}` selected in {}'.format(f.from_output, self._parent))
exit(WRONG_ARGUMENTS)
GS.exit_with_error(f'Unknown output `{f.from_output}` selected in {self._parent}', WRONG_ARGUMENTS)
if not no_out_run:
for file in files_list:
if not os.path.isfile(file):
@ -81,8 +80,7 @@ class PDFUniteOptions(BaseOptions):
run_output(out)
if not os.path.isfile(file):
# Still missing, something is wrong
logger.error('Unable to generate `{}` from {}'.format(file, out))
exit(INTERNAL_ERROR)
GS.exit_with_error('Unable to generate `{file}` from {out}', INTERNAL_ERROR)
else:
out_dir = out_dir_cwd if f.from_cwd else out_dir_default
source = f.expand_filename_both(f.source, make_safe=False)
@ -113,8 +111,7 @@ class PDFUniteOptions(BaseOptions):
try:
check_output(cmd, stderr=STDOUT)
except FileNotFoundError:
logger.error('Missing `pdfunite` command, install it (poppler-utils)')
exit(MISSING_TOOL)
GS.exit_with_error('Missing `pdfunite` command, install it (poppler-utils)', MISSING_TOOL)
except CalledProcessError as e:
logger.error('Failed to invoke pdfunite command, error {}'.format(e.returncode))
if e.output:
@ -132,8 +129,7 @@ class PDFUniteOptions(BaseOptions):
if sig != b'%PDF':
logger.warning(W_NOTPDF+'Joining a non PDF file `{}`, will most probably fail'.format(fn))
if len(files) < 2:
logger.error('At least two files must be joined ({})'.format(files))
exit(MISSING_FILES)
GS.exit_with_error(f'At least two files must be joined ({files})', MISSING_FILES)
logger.debug('Generating `{}` PDF'.format(output))
if os.path.isfile(output):
os.remove(output)

View File

@ -263,9 +263,8 @@ class Render3DOptions(Base3DOptionsWithHL):
def run(self, output):
super().run(output)
if GS.ki6 and GS.kicad_version_n < KICAD_VERSION_6_0_2:
logger.error("3D Viewer not supported for KiCad 6.0.0/1\n"
"Please upgrade KiCad to 6.0.2 or newer")
exit(MISSING_TOOL)
GS.exit_with_error("3D Viewer not supported for KiCad 6.0.0/1\n"
"Please upgrade KiCad to 6.0.2 or newer", MISSING_TOOL)
command = self.ensure_tool('KiAuto')
if self.transparent_background:
# Use the chroma key color

View File

@ -284,10 +284,10 @@ class ReportOptions(BaseOptions):
rep = str(val)
line = line.replace('${'+var_ori+'}', rep)
else:
logger.error('Unable to expand `{}`'.format(var))
logger.non_critical_error('Unable to expand `{}`'.format(var))
if not self._shown_defined:
self._shown_defined = True
logger.error('Defined values: {}'.format([v for v in defined.keys() if v[0] != '_']))
logger.non_critical_error('Defined values: {}'.format([v for v in defined.keys() if v[0] != '_']))
return line
def context_defined_tracks(self, line):

View File

@ -5,7 +5,6 @@
# Project: KiBot (formerly KiPlot)
import os
import re
import sys
from subprocess import run, PIPE
from .error import KiPlotConfigurationError
from .misc import FAILED_EXECUTE, W_EMPTREP, W_BADCHARS
@ -107,8 +106,7 @@ class Base_Replace(BasePreFlight): # noqa: F821
logger.debugl(2, 'Running: {}'.format(cmd))
result = run(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True)
if result.returncode:
logger.error('Failed to execute:\n{}\nreturn code {}'.format(r.command, result.returncode))
sys.exit(FAILED_EXECUTE)
GS.exit_with_error('Failed to execute:\n{r.command}\nreturn code {result.returncode}', FAILED_EXECUTE)
if not result.stdout:
logger.warning(W_EMPTREP+"Empty value from `{}` skipping it".format(r.command))
continue

View File

@ -9,7 +9,7 @@ from .gs import GS
from .registrable import Registrable
from .optionable import Optionable
from .error import PlotError, KiPlotConfigurationError
from .misc import PLOT_ERROR, EXIT_BAD_CONFIG
from .misc import PLOT_ERROR, EXIT_BAD_CONFIG, W_KEEPTMP
from .log import get_logger
logger = get_logger(__name__)
@ -88,11 +88,9 @@ class BasePreFlight(Registrable):
logger.debug('Preflight run '+k)
v.run()
except PlotError as e:
logger.error("In preflight `"+str(k)+"`: "+str(e))
exit(PLOT_ERROR)
GS.exit_with_error("In preflight `"+str(k)+"`: "+str(e), PLOT_ERROR)
except KiPlotConfigurationError as e:
logger.error("In preflight `"+str(k)+"`: "+str(e))
exit(EXIT_BAD_CONFIG)
GS.exit_with_error("In preflight `"+str(k)+"`: "+str(e), EXIT_BAD_CONFIG)
def disable(self):
self._enabled = False
@ -185,7 +183,7 @@ class BasePreFlight(Registrable):
finally:
if GS.debug_enabled and not remove_tmps:
if self._files_to_remove:
logger.error('Keeping temporal files: '+str(self._files_to_remove))
logger.warning(W_KEEPTMP+'Keeping temporal files: '+str(self._files_to_remove))
else:
self.remove_temporals()
if self._files_to_remove:

View File

@ -10,7 +10,6 @@ Dependencies:
version: 2.0.0
"""
import os
from sys import exit
from .macros import macros, pre_class # noqa: F401
from .error import KiPlotConfigurationError
from .gs import GS
@ -79,7 +78,7 @@ class Run_DRC(BasePreFlight): # noqa: F821
if ret > 127:
ret = -(256-ret)
if ret < 0:
logger.error('DRC violations: %d', -ret)
msg = f'DRC violations: {-ret}'
else:
logger.error('DRC returned %d', ret)
exit(DRC_ERROR)
msg = f'DRC returned {ret}'
GS.exit_with_error(msg, DRC_ERROR)

View File

@ -12,7 +12,6 @@ Dependencies:
"""
import os
from shutil import move
from sys import exit
from tempfile import NamedTemporaryFile
from .macros import macros, pre_class # noqa: F401
from .gs import GS
@ -77,9 +76,9 @@ class Run_ERC(BasePreFlight): # noqa: F821
if ret > 127:
ret = -(256-ret)
if ret < 0:
logger.error('ERC errors: %d', -ret)
msgs = [f'ERC errors: {-ret}']
else:
logger.error('ERC returned %d', ret)
msgs = [f'ERC returned {ret}']
if GS.sch.annotation_error:
logger.error('Make sure your schematic is fully annotated')
exit(ERC_ERROR)
msgs.append('Make sure your schematic is fully annotated')
GS.exit_with_error(msgs, ERC_ERROR)

View File

@ -14,7 +14,6 @@ import json
import os
import re
from subprocess import run, PIPE
import sys
from .error import KiPlotConfigurationError
from .misc import FAILED_EXECUTE, W_EMPTREP
from .optionable import Optionable
@ -135,12 +134,12 @@ class Set_Text_Variables(BasePreFlight): # noqa: F821
logger.debug('Executing: '+GS.pasteable_cmd(command))
result = run(cmd, stdout=PIPE, stderr=PIPE, universal_newlines=True)
if result.returncode:
logger.error('Failed to execute:\n{}\nreturn code {}'.format(r.command, result.returncode))
msgs = [f'Failed to execute:\n{r.command}\nreturn code {result.returncode}']
if result.stdout:
logger.error('stdout:\n{}'.format(result.stdout))
msgs.append(f'stdout:\n{result.stdout}')
if result.stderr:
logger.error('stderr:\n{}'.format(result.stderr))
sys.exit(FAILED_EXECUTE)
msgs.append(f'stderr:\n{result.stderr}')
GS.exit_with_error(msgs, FAILED_EXECUTE)
if not result.stdout:
logger.warning(W_EMPTREP+"Empty value from `{}`".format(r.command))
text = result.stdout.strip()

View File

@ -12,7 +12,6 @@ Dependencies:
"""
from collections import namedtuple
import os
from sys import exit
import xml.etree.ElementTree as ET
from .macros import macros, document, pre_class # noqa: F401
from .error import KiPlotConfigurationError
@ -160,12 +159,10 @@ class Update_XML(BasePreFlight): # noqa: F821
def check_pcb_parity(self):
if GS.ki5:
logger.error('PCB vs schematic parity only available for KiCad 6')
exit(MISSING_TOOL)
GS.exit_with_error('PCB vs schematic parity only available for KiCad 6', MISSING_TOOL)
if GS.ki7 and GS.kicad_version_n < KICAD_VERSION_7_0_1:
logger.error("Connectivity API is broken on KiCad 7.0.0\n"
"Please upgrade KiCad to 7.0.1 or newer")
exit(MISSING_TOOL)
GS.exit_with_error("Connectivity API is broken on KiCad 7.0.0\n"
"Please upgrade KiCad to 7.0.1 or newer", MISSING_TOOL)
fname = GS.sch_no_ext+'.xml'
logger.debug('Loading XML: '+fname)
try:
@ -208,9 +205,7 @@ class Update_XML(BasePreFlight): # noqa: F821
for e in errors:
logger.warning(W_PARITY+e)
else:
for e in errors:
logger.error(e)
exit(NETLIST_DIFF)
GS.exit_with_error(errors, NETLIST_DIFF)
def run(self):
command = self.ensure_tool('KiAuto')