diff --git a/kibot/__main__.py b/kibot/__main__.py index 8e84ad12..a6655a23 100644 --- a/kibot/__main__.py +++ b/kibot/__main__.py @@ -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: diff --git a/kibot/bom/bom_writer.py b/kibot/bom/bom_writer.py index 0b47ea9c..4f393c27 100644 --- a/kibot/bom/bom_writer.py +++ b/kibot/bom/bom_writer.py @@ -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 diff --git a/kibot/bom/units.py b/kibot/bom/units.py index bc98dab3..580ea618 100644 --- a/kibot/bom/units.py +++ b/kibot/bom/units.py @@ -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 diff --git a/kibot/bom/xlsx_writer.py b/kibot/bom/xlsx_writer.py index 855d8879..2a28683d 100644 --- a/kibot/bom/xlsx_writer.py +++ b/kibot/bom/xlsx_writer.py @@ -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 diff --git a/kibot/config_reader.py b/kibot/config_reader.py index 130abd1e..b52a08d6 100644 --- a/kibot/config_reader.py +++ b/kibot/config_reader.py @@ -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") diff --git a/kibot/dep_downloader.py b/kibot/dep_downloader.py index d0ad6596..2c7565f8 100644 --- a/kibot/dep_downloader.py +++ b/kibot/dep_downloader.py @@ -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) diff --git a/kibot/fil_spec_to_field.py b/kibot/fil_spec_to_field.py index 5051381b..4d4830e3 100644 --- a/kibot/fil_spec_to_field.py +++ b/kibot/fil_spec_to_field.py @@ -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() diff --git a/kibot/fil_subparts.py b/kibot/fil_subparts.py index 8232cab8..9955a6c3 100644 --- a/kibot/fil_subparts.py +++ b/kibot/fil_subparts.py @@ -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 """ diff --git a/kibot/kicad/config.py b/kibot/kicad/config.py index 69912d0a..59038da8 100644 --- a/kibot/kicad/config.py +++ b/kibot/kicad/config.py @@ -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' diff --git a/kibot/kicad/v5_sch.py b/kibot/kicad/v5_sch.py index 8e7734b7..d3811a71 100644 --- a/kibot/kicad/v5_sch.py +++ b/kibot/kicad/v5_sch.py @@ -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): diff --git a/kibot/kiplot.py b/kibot/kiplot.py index 86c15334..2f05afb1 100644 --- a/kibot/kiplot.py +++ b/kibot/kiplot.py @@ -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({}) diff --git a/kibot/log.py b/kibot/log.py index 55f63319..3550ceee 100644 --- a/kibot/log.py +++ b/kibot/log.py @@ -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 diff --git a/kibot/misc.py b/kibot/misc.py index 801b16d0..00296fe3 100644 --- a/kibot/misc.py +++ b/kibot/misc.py @@ -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", diff --git a/kibot/out_any_layer.py b/kibot/out_any_layer.py index 159b8726..fa868d02 100644 --- a/kibot/out_any_layer.py +++ b/kibot/out_any_layer.py @@ -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 diff --git a/kibot/out_any_pcb_print.py b/kibot/out_any_pcb_print.py index 973f558c..a4cd30cd 100644 --- a/kibot/out_any_pcb_print.py +++ b/kibot/out_any_pcb_print.py @@ -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] diff --git a/kibot/out_base.py b/kibot/out_base.py index f1f237ce..5dcfa898 100644 --- a/kibot/out_base.py +++ b/kibot/out_base.py @@ -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 diff --git a/kibot/out_base_3d.py b/kibot/out_base_3d.py index 2318f327..d769b800 100644 --- a/kibot/out_base_3d.py +++ b/kibot/out_base_3d.py @@ -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 diff --git a/kibot/out_blender_export.py b/kibot/out_blender_export.py index 617539d4..2de8aa9f 100644 --- a/kibot/out_blender_export.py +++ b/kibot/out_blender_export.py @@ -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 diff --git a/kibot/out_compress.py b/kibot/out_compress.py index 8d78500c..785b86b7 100644 --- a/kibot/out_compress.py +++ b/kibot/out_compress.py @@ -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: diff --git a/kibot/out_copy_files.py b/kibot/out_copy_files.py index 13fc08d0..c7950bc6 100644 --- a/kibot/out_copy_files.py +++ b/kibot/out_copy_files.py @@ -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): diff --git a/kibot/out_diff.py b/kibot/out_diff.py index 6e18bf45..2ec3d0fa 100644 --- a/kibot/out_diff.py +++ b/kibot/out_diff.py @@ -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()) diff --git a/kibot/out_kicost.py b/kibot/out_kicost.py index 5d47a777..84218b20 100644 --- a/kibot/out_kicost.py +++ b/kibot/out_kicost.py @@ -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): diff --git a/kibot/out_navigate_results.py b/kibot/out_navigate_results.py index f766cccc..712ea838 100644 --- a/kibot/out_navigate_results.py +++ b/kibot/out_navigate_results.py @@ -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' diff --git a/kibot/out_panelize.py b/kibot/out_panelize.py index 9a8d3a62..7cdecb6e 100644 --- a/kibot/out_panelize.py +++ b/kibot/out_panelize.py @@ -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() diff --git a/kibot/out_pcb2blender_tools.py b/kibot/out_pcb2blender_tools.py index 937f4a18..e3d85c91 100644 --- a/kibot/out_pcb2blender_tools.py +++ b/kibot/out_pcb2blender_tools.py @@ -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) diff --git a/kibot/out_pcb_print.py b/kibot/out_pcb_print.py index 14b96062..df4df662 100644 --- a/kibot/out_pcb_print.py +++ b/kibot/out_pcb_print.py @@ -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 diff --git a/kibot/out_pcbdraw.py b/kibot/out_pcbdraw.py index 7abab3c8..8009c874 100644 --- a/kibot/out_pcbdraw.py +++ b/kibot/out_pcbdraw.py @@ -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) diff --git a/kibot/out_pdfunite.py b/kibot/out_pdfunite.py index 0b0fbe48..6b91023a 100644 --- a/kibot/out_pdfunite.py +++ b/kibot/out_pdfunite.py @@ -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) diff --git a/kibot/out_render_3d.py b/kibot/out_render_3d.py index bd8fa630..6e47623a 100644 --- a/kibot/out_render_3d.py +++ b/kibot/out_render_3d.py @@ -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 diff --git a/kibot/out_report.py b/kibot/out_report.py index 81586024..ad16262f 100644 --- a/kibot/out_report.py +++ b/kibot/out_report.py @@ -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): diff --git a/kibot/pre_any_replace.py b/kibot/pre_any_replace.py index 924198b6..cca54d4d 100644 --- a/kibot/pre_any_replace.py +++ b/kibot/pre_any_replace.py @@ -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 diff --git a/kibot/pre_base.py b/kibot/pre_base.py index 2d2892f7..f2611e18 100644 --- a/kibot/pre_base.py +++ b/kibot/pre_base.py @@ -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: diff --git a/kibot/pre_run_drc.py b/kibot/pre_run_drc.py index 98247ce0..b5025fe6 100644 --- a/kibot/pre_run_drc.py +++ b/kibot/pre_run_drc.py @@ -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) diff --git a/kibot/pre_run_erc.py b/kibot/pre_run_erc.py index 7e8b83b7..14a15efb 100644 --- a/kibot/pre_run_erc.py +++ b/kibot/pre_run_erc.py @@ -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) diff --git a/kibot/pre_set_text_variables.py b/kibot/pre_set_text_variables.py index 884963f4..bed46fb1 100644 --- a/kibot/pre_set_text_variables.py +++ b/kibot/pre_set_text_variables.py @@ -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() diff --git a/kibot/pre_update_xml.py b/kibot/pre_update_xml.py index 527c6041..3df64a84 100644 --- a/kibot/pre_update_xml.py +++ b/kibot/pre_update_xml.py @@ -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')