# -*- coding: utf-8 -*- # Copyright (c) 2022 Salvador E. Tropea # Copyright (c) 2022 Instituto Nacional de TecnologĂ­a Industrial # License: GPL-3.0 # Project: KiBot (formerly KiPlot) """ Dependencies: - from: Git role: Find commit hash and/or date - from: Bash role: Run external commands to create replacement text """ import json import os import re import shlex from subprocess import run, PIPE import sys from .error import KiPlotConfigurationError from .misc import FAILED_EXECUTE, W_EMPTREP from .optionable import Optionable from .pre_base import BasePreFlight from .gs import GS from .macros import macros, document, pre_class # noqa: F401 from . import log logger = log.get_logger() re_git = re.compile(r'([^a-zA-Z_]|^)(git) ') class KiCadVariable(Optionable): """ KiCad variable definition """ def __init__(self): super().__init__() self._unknown_is_error = True with document: self.name = '' """ Name of the variable. The `version` variable will be expanded using `${version}` """ self.variable = None """ {name} """ self.text = '' """ Text to insert instead of the variable """ self.command = '' """ Command to execute to get the text, will be used only if `text` is empty. This command will be executed using the Bash shell. Be careful about spaces in file names (i.e. use "$KIBOT_PCB_NAME"). The `KIBOT_PCB_NAME` environment variable is the PCB file and the `KIBOT_SCH_NAME` environment variable is the schematic file """ self.before = '' """ Text to add before the output of `command` """ self.after = '' """ Text to add after the output of `command` """ self.expand_kibot_patterns = True """ Expand %X patterns. The context is `schematic` """ def config(self, parent): super().config(parent) if not self.name: raise KiPlotConfigurationError("Missing variable name ({})".format(str(self._tree))) class Set_Text_VariablesOptions(Optionable): """ A list of KiCad variables """ def __init__(self): super().__init__() with document: self.variables = KiCadVariable """ [dict|list(dict)] Variables """ def config(self, parent): super().config(parent) if isinstance(self.variables, type): self.variables = [] elif isinstance(self.variables, KiCadVariable): self.variables = [self.variables] @pre_class class Set_Text_Variables(BasePreFlight): # noqa: F821 """ [dict|list(dict)] Defines KiCad 6 variables. They are expanded using ${VARIABLE}, and stored in the project file. This preflight replaces `pcb_replace` and `sch_replace` when using KiCad 6. The KiCad project file is modified """ def __init__(self, name, value): f = Set_Text_VariablesOptions() f.set_tree({'variables': value}) f.config(self) super().__init__(name, f.variables) @classmethod def get_doc(cls): return cls.__doc__, KiCadVariable @classmethod def get_example(cls): """ Returns a YAML value for the example config """ return ("\n - name: 'git_hash'" "\n command: 'git log -1 --format=\"%h\" \"$KIBOT_PCB_NAME\"'" "\n before: 'Git hash: <'" "\n after: '>'") def apply(self): o = self._value if len(o) == 0: return if GS.ki5: raise KiPlotConfigurationError("The `set_text_variables` preflight is for KiCad 6 or newer") pro_name = GS.pro_file if not pro_name or not os.path.isfile(pro_name): raise KiPlotConfigurationError("Trying to define KiCad 6 variables but the project is missing ({})". format(pro_name)) # Get the current definitions with open(pro_name, 'rt') as f: pro_text = f.read() data = json.loads(pro_text) text_variables = data.get('text_variables', {}) GS.pro_variables = text_variables logger.debug("- Current variables: {}".format(text_variables)) # Define the requested variables if GS.pcb_file: os.environ['KIBOT_PCB_NAME'] = GS.pcb_file if GS.sch_file: os.environ['KIBOT_SCH_NAME'] = GS.sch_file bash_command = None for r in o: text = r.text if not text and r.command: command = r.command if re_git.search(command): git_command = self.ensure_tool('git') command = re_git.sub(r'\1'+git_command.replace('\\', r'\\')+' ', command) if not bash_command: bash_command = self.ensure_tool('Bash') cmd = [bash_command, '-c', command] logger.debug('Executing: '+shlex.join(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)) if result.stdout: logger.error('stdout:\n{}'.format(result.stdout)) if result.stderr: logger.error('stderr:\n{}'.format(result.stderr)) sys.exit(FAILED_EXECUTE) if not result.stdout: logger.warning(W_EMPTREP+"Empty value from `{}`".format(r.command)) text = result.stdout.strip() text = r.before + text + r.after logger.debug(' - ' + r.name + ' -> ' + text) text_variables[r.name] = text logger.debug("- Expanding %X patterns in variables") # Now that we have the variables defined expand the %X patterns (they could use variables) for r in o: if r.expand_kibot_patterns: text = text_variables[r.name] new_text = Optionable.expand_filename_both(self, text, make_safe=False) if text != new_text: logger.debug(' - ' + r.name + ' -> ' + new_text) text_variables[r.name] = new_text logger.debug("- New list of variables: {}".format(text_variables)) # Store the modified project data['text_variables'] = text_variables GS.make_bkp(pro_name) with open(pro_name, 'wt') as f: f.write(json.dumps(data, sort_keys=True, indent=2)) if GS.board: # Force a project and PCB reload GS.reload_project(pro_name)