Added `kibot-check` tool to check the installation

This commit is contained in:
Salvador E. Tropea 2022-05-20 13:26:41 -03:00
parent 5bcedf4c7a
commit fb082fcbb3
23 changed files with 1551 additions and 44 deletions

View File

@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- `kibot-check` tool to check the installation
- New outputs:
- KiCad netlist generation
- IPC-D-356 netlist generation (#197)

View File

@ -93,14 +93,15 @@ You can also run KiBot using docker images in a CI/CD environment like GitHub or
Notes:
- When installing from the Debian repo you don't need to worry about dependencies, just pay attention to *recommended* and *suggested* packages.
- When installing using `pip` the dependencies marked as **PyPi dependency** will be automatically installed.
- The `kibot-check` tool can help you to know which dependencies are missing.
[**distutils**](https://pypi.org/project/distutils/) (python module) [Debian](https://packages.debian.org/bullseye/python3-distutils)
[**Distutils**](https://pypi.org/project/Distutils/) (python module) [Debian](https://packages.debian.org/bullseye/python3-distutils)
- Mandatory
[**PyYAML**](https://pypi.org/project/PyYAML/) (python module) (PyPi dependency) [Debian](https://packages.debian.org/bullseye/python3-yaml)
- Mandatory
[**requests**](https://pypi.org/project/requests/) (python module) (PyPi dependency) [Debian](https://packages.debian.org/bullseye/python3-requests)
[**Requests**](https://pypi.org/project/Requests/) (python module) (PyPi dependency) [Debian](https://packages.debian.org/bullseye/python3-requests)
- Mandatory
[**KiCad Automation tools**](https://github.com/INTI-CMNB/KiAuto) v1.6.11 (tool) (PyPi dependency)
@ -110,24 +111,35 @@ Notes:
- Mandatory for `kicost`
- Optional to find components costs and specs for `bom`
[**Interactive HTML BoM**](https://github.com/INTI-CMNB/InteractiveHtmlBom) v2.4.1.3 (tool)
[**Interactive HTML BoM**](https://github.com/INTI-CMNB/InteractiveHtmlBom) v2.4.1.4 (tool)
- Mandatory for `ibom`
[**KiBoM**](https://github.com/INTI-CMNB/KiBoM) v1.8.0 (tool)
- Mandatory for `kibom`
[**lxml**](https://pypi.org/project/lxml/) (python module) [Debian](https://packages.debian.org/bullseye/python3-lxml)
[**LXML**](https://pypi.org/project/LXML/) (python module) [Debian](https://packages.debian.org/bullseye/python3-lxml)
- Mandatory for `pcb_print`
[**PcbDraw**](https://github.com/INTI-CMNB/pcbdraw) v0.9.0 (tool)
- Mandatory for `pcbdraw`
[**qrcodegen**](https://pypi.org/project/qrcodegen/) (python module) (PyPi dependency) [Debian](https://packages.debian.org/bullseye/python3-qrcodegen)
[**QRCodeGen**](https://pypi.org/project/QRCodeGen/) (python module) (PyPi dependency) [Debian](https://packages.debian.org/bullseye/python3-qrcodegen)
- Mandatory for `qr_lib`
[**colorama**](https://pypi.org/project/colorama/) (python module) (PyPi dependency) [Debian](https://packages.debian.org/bullseye/python3-colorama)
[**Colorama**](https://pypi.org/project/Colorama/) (python module) (PyPi dependency) [Debian](https://packages.debian.org/bullseye/python3-colorama)
- Optional to get color messages in a portable way for general use
[**Git**](https://git-scm.com/) (tool) [Debian](https://packages.debian.org/bullseye/git)
- Optional to:
- Find commit hash and/or date for `pcb_replace`
- Find commit hash and/or date for `sch_replace`
- Find commit hash and/or date for `set_text_variables`
[**ImageMagick**](https://imagemagick.org/) (tool) [Debian](https://packages.debian.org/bullseye/imagemagick)
- Optional to:
- Create monochrome prints for `pcb_print`
- Create JPG images for `pcbdraw`
[**RSVG tools**](https://cran.r-project.org/web/packages/rsvg/index.html) (tool) [Debian](https://packages.debian.org/bullseye/librsvg2-bin)
- Optional to:
- Create PDF, PNG, EPS and PS formats for `pcb_print`
@ -136,16 +148,13 @@ Notes:
[**Ghostscript**](https://www.ghostscript.com/) (tool) [Debian](https://packages.debian.org/bullseye/ghostscript)
- Optional to create PS files for `pcb_print`
[**ImageMagick**](https://imagemagick.org/) (tool) [Debian](https://packages.debian.org/bullseye/imagemagick)
- Optional to create JPG images for `pcbdraw`
[**Pandoc**](https://pandoc.org/) (tool) [Debian](https://packages.debian.org/bullseye/pandoc)
- Optional to create PDF/ODF/DOCX files for `report`
[**RAR**](https://www.rarlab.com/) (tool) [Debian](https://packages.debian.org/bullseye/rar)
- Optional to compress in RAR format for `compress`
[**xlsxwriter**](https://pypi.org/project/xlsxwriter/) (python module) (PyPi dependency) [Debian](https://packages.debian.org/bullseye/python3-xlsxwriter)
[**XLSXWriter**](https://pypi.org/project/XLSXWriter/) (python module) (PyPi dependency) [Debian](https://packages.debian.org/bullseye/python3-xlsxwriter)
- Optional to create XLSX files for `bom`
@ -3026,7 +3035,7 @@ Usage:
kibot [-v...] [--start PATH] [-d OUT_DIR] [--dry] [-t, --type TYPE]...
--quick-start
kibot [-v...] --help-filters
kibot [-v...] [--markdown] --help-dependencies
kibot [-v...] [--markdown|--json] --help-dependencies
kibot [-v...] --help-global-options
kibot [-v...] --help-list-outputs
kibot [-v...] --help-output=HELP_OUTPUT

View File

@ -2,7 +2,7 @@
all: ../README.md samples/generic_plot.kibot.yaml ../kibot/report_templates/report_full_svg.txt ../kibot/config_templates/bom/MacroFab_XYRS.kibot.yaml \
../kibot/config_templates/gerber/Elecrow.kibot.yaml ../kibot/config_templates/gerber/FusionPCB.kibot.yaml \
../kibot/config_templates/gerber/JLCPCB.kibot.yaml ../kibot/config_templates/gerber/PCBWay.kibot.yaml
../kibot/config_templates/gerber/JLCPCB.kibot.yaml ../kibot/config_templates/gerber/PCBWay.kibot.yaml ../src/kibot-check
../kibot/config_templates/gerber/%.kibot.yaml: samples/%.kibot.yaml
cp $< $@
@ -13,6 +13,9 @@ all: ../README.md samples/generic_plot.kibot.yaml ../kibot/report_templates/rep
../README.md: README.in replace_tags.pl ../kibot/out_*.py ../kibot/pre_*.py ../kibot/fil_*.py ../kibot/__main__.py ../kibot/config_reader.py
cat README.in | perl replace_tags.pl > ../README.md
../src/kibot-check: ../src/kibot-check.in replace_tags.pl ../kibot/out_*.py ../kibot/pre_*.py ../kibot/registrable.py ../kibot/misc.py ../kibot/config_reader.py
cat ../src/kibot-check.in | perl replace_tags.pl > ../src/kibot-check
samples/generic_plot.kibot.yaml: ../kibot/out_*.py ../kibot/pre_*.py ../kibot/config_reader.py
rm -f example_template.kibot.yaml
../src/kibot -v --example

View File

@ -93,6 +93,7 @@ You can also run KiBot using docker images in a CI/CD environment like GitHub or
Notes:
- When installing from the Debian repo you don't need to worry about dependencies, just pay attention to *recommended* and *suggested* packages.
- When installing using `pip` the dependencies marked as **PyPi dependency** will be automatically installed.
- The `kibot-check` tool can help you to know which dependencies are missing.
@dependencies@

View File

@ -6,6 +6,8 @@ $preflight=`../src/kibot --help-preflights`;
$filters=`../src/kibot --help-filters`;
$global_options=`../src/kibot --help-global-options`;
$dependencies=`../src/kibot --help-dependencies --markdown`;
$json_dep=`../src/kibot --help-dependencies --json`;
$json_dep=~s/\n/\\\n/g;
while (<>)
{
@ -15,6 +17,7 @@ while (<>)
$_ =~ s/\@filters\@/$filters/;
$_ =~ s/\@global_options\@/$global_options/;
$_ =~ s/\@dependencies\@/$dependencies/;
$_ =~ s/\@json_dep\@/$json_dep/;
print $_;
}

View File

@ -15,7 +15,7 @@ Usage:
kibot [-v...] [--start PATH] [-d OUT_DIR] [--dry] [-t, --type TYPE]...
--quick-start
kibot [-v...] --help-filters
kibot [-v...] [--markdown] --help-dependencies
kibot [-v...] [--markdown|--json] --help-dependencies
kibot [-v...] --help-global-options
kibot [-v...] --help-list-outputs
kibot [-v...] --help-output=HELP_OUTPUT
@ -88,9 +88,9 @@ if os.environ.get('KIAUS_USE_NIGHTLY'): # pragma: no cover (nightly)
else:
os.environ['PYTHONPATH'] = pcbnew_path
nightly = True
from .gs import (GS)
from .misc import (EXIT_BAD_ARGS, W_VARCFG, NO_PCBNEW_MODULE, W_NOKIVER, hide_stderr)
from .pre_base import (BasePreFlight)
from .gs import GS
from .misc import EXIT_BAD_ARGS, W_VARCFG, NO_PCBNEW_MODULE, W_NOKIVER, hide_stderr, TRY_INSTALL_CHECK
from .pre_base import BasePreFlight
from .config_reader import (CfgYamlReader, print_outputs_help, print_output_help, print_preflights_help, create_example,
print_filters_help, print_global_options_help, print_dependencies)
from .kiplot import (generate_outputs, load_actions, config_output, generate_makefile, generate_examples, solve_schematic,
@ -157,6 +157,7 @@ def detect_kicad():
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)
try:
GS.kicad_version = pcbnew.GetBuildVersion()
@ -267,7 +268,7 @@ def main():
print_global_options_help()
sys.exit(0)
if args.help_dependencies:
print_dependencies(args.markdown)
print_dependencies(args.markdown, args.json)
sys.exit(0)
if args.example:
check_board_file(args.board_file)

View File

@ -18,7 +18,7 @@ from base64 import b64decode
from .columnlist import ColumnList
from .kibot_logo import KIBOT_LOGO
from .. import log
from ..misc import W_NOKICOST, W_UNKDIST, KICOST_ERROR, W_BADFIELD
from ..misc import W_NOKICOST, W_UNKDIST, KICOST_ERROR, W_BADFIELD, TRY_INSTALL_CHECK
from ..error import trace_dump
from ..gs import GS
from .. import __version__
@ -51,6 +51,7 @@ except ModuleNotFoundError:
except ImportError:
logger.error("Installed KiCost is older than the version we support.")
logger.error("Try installing the last release or the current GIT code.")
logger.error(TRY_INSTALL_CHECK)
KICOST_SUPPORT = False
ProgressConsole = object

View File

@ -10,12 +10,13 @@ Class to read KiBot config files
"""
import os
import json
from sys import (exit, maxsize)
from collections import OrderedDict
from .error import (KiPlotConfigurationError, config_error)
from .misc import (NO_YAML_MODULE, EXIT_BAD_ARGS, EXAMPLE_CFG, WONT_OVERWRITE, W_NOOUTPUTS, W_UNKOUT, W_NOFILTERS,
W_NOVARIANTS, W_NOGLOBALS)
W_NOVARIANTS, W_NOGLOBALS, TRY_INSTALL_CHECK)
from .gs import GS
from .registrable import RegOutput, RegVariant, RegFilter, RegDependency
from .pre_base import BasePreFlight
@ -35,6 +36,7 @@ try:
except ImportError:
log.init()
logger.error('No yaml module for Python, install python3-yaml')
logger.error(TRY_INSTALL_CHECK)
exit(NO_YAML_MODULE)
@ -694,7 +696,13 @@ def global2human(name):
return '`'+name+'`' if name != 'global' else 'general use'
def print_dependencies(markdown=True):
class MyEncoder(json.JSONEncoder):
""" Simple JSON encoder for objects """
def default(self, o):
return o.__dict__
def print_dependencies(markdown=True, jsn=False):
# Compute the importance of each dependency
for dep in RegDependency.get_registered().values():
importance = 0
@ -705,6 +713,10 @@ def print_dependencies(markdown=True):
else:
importance += LOCAL_OPTIONAL if local else GLOBAL_OPTIONAL
dep.importance = importance
# The JSON output is just a dump
if jsn:
print(json.dumps(RegDependency.get_registered(), cls=MyEncoder, indent=4, sort_keys=True))
return
# Now print them sorted by importance (and by name as a second criteria)
for name, dep in sorted(sorted(RegDependency.get_registered().items(), key=lambda x: x[0].lower()), # noqa C414
key=lambda x: x[1].importance, reverse=True):

View File

@ -23,7 +23,7 @@ from collections import OrderedDict
from .gs import GS
from .registrable import RegOutput
from .misc import (PLOT_ERROR, MISSING_TOOL, CMD_EESCHEMA_DO, URL_EESCHEMA_DO, CORRUPTED_PCB,
EXIT_BAD_ARGS, CORRUPTED_SCH, EXIT_BAD_CONFIG, WRONG_INSTALL, UI_SMD, UI_VIRTUAL,
EXIT_BAD_ARGS, CORRUPTED_SCH, EXIT_BAD_CONFIG, WRONG_INSTALL, UI_SMD, UI_VIRTUAL, TRY_INSTALL_CHECK,
MOD_SMD, MOD_THROUGH_HOLE, 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)
from .error import PlotError, KiPlotConfigurationError, config_error, trace_dump
@ -44,6 +44,7 @@ try:
except ImportError:
log.init()
logger.error('No yaml module for Python, install python3-yaml')
logger.error(TRY_INSTALL_CHECK)
exit(NO_YAML_MODULE)
@ -122,6 +123,7 @@ def check_script(cmd, url, version=None):
if which(cmd) is None:
logger.error('No `'+cmd+'` command found.\n'
'Please install it, visit: '+url)
logger.error(TRY_INSTALL_CHECK)
exit(MISSING_TOOL)
if version is not None:
check_version(cmd, version)

View File

@ -95,6 +95,7 @@ PANDOC = 'pandoc'
KICAD_VERSION_5_99 = 5099000
KICAD_VERSION_6_0_0 = 6000000
KICAD_VERSION_6_0_2 = 6000002
TRY_INSTALL_CHECK = 'Try running the installation checker: kibot-check'
# Internal filter names
IFILT_MECHANICAL = '_mechanical'
@ -311,7 +312,8 @@ class ToolDependencyRole(object):
class ToolDependency(object):
""" Class used to define tools needed for an output """
def __init__(self, output, name, url=None, url_down=None, is_python=False, deb=None, in_debian=True, extra_deb=None,
roles=None, is_kicad_plugin=False, command=None, pypi_name=None):
roles=None, plugin_dirs=None, command=None, pypi_name=None, module_name=None, no_cmd_line_version=False,
help_option=None, no_cmd_line_version_old=False):
# The associated output
self.output = output
# Name of the tool
@ -325,6 +327,8 @@ class ToolDependency(object):
else:
self.deb_package = deb
self.is_python = is_python
if is_python:
self.module_name = module_name if module_name is not None else name.lower()
# If this tool has an official Debian package
self.in_debian = in_debian
# Name at PyPi, can be fake for things that aren't at PyPi
@ -336,9 +340,13 @@ class ToolDependency(object):
self.url = url
self.url_down = url_down
# Can be installed as a KiCad plug-in?
self.is_kicad_plugin = is_kicad_plugin
self.is_kicad_plugin = plugin_dirs is not None
self.plugin_dirs = plugin_dirs
# Command we run
self.command = command
self.command = command if command is not None else name.lower()
self.no_cmd_line_version = no_cmd_line_version
self.no_cmd_line_version_old = no_cmd_line_version_old # An old version doesn't have version
self.help_option = help_option if help_option is not None else '--version'
# Roles
if roles is None:
roles = [ToolDependencyRole()]
@ -352,4 +360,9 @@ class ToolDependency(object):
def kiauto_dependency(output, version=None):
role = None if version is None else ToolDependencyRole(version=version)
return ToolDependency(output, 'KiCad Automation tools', URL_EESCHEMA_DO, url_down=URL_EESCHEMA_DO+'/releases',
in_debian=False, pypi_name='kiauto', roles=role)
in_debian=False, pypi_name='kiauto', command='pcbnew_do', roles=role)
def git_dependency(output):
return ToolDependency(output, 'Git', 'https://git-scm.com/',
roles=ToolDependencyRole(desc='Find commit hash and/or date'))

View File

@ -14,7 +14,8 @@ from tarfile import open as tar_open
from collections import OrderedDict
from .gs import GS
from .kiplot import config_output, get_output_dir, run_output
from .misc import MISSING_TOOL, WRONG_INSTALL, W_EMPTYZIP, WRONG_ARGUMENTS, INTERNAL_ERROR, ToolDependency, ToolDependencyRole
from .misc import (MISSING_TOOL, WRONG_INSTALL, W_EMPTYZIP, WRONG_ARGUMENTS, INTERNAL_ERROR, ToolDependency,
ToolDependencyRole, TRY_INSTALL_CHECK)
from .optionable import Optionable, BaseOptions
from .registrable import RegOutput, RegDependency
from .macros import macros, document, output_class # noqa: F401
@ -22,7 +23,7 @@ from . import log
logger = log.get_logger()
RegDependency.register(ToolDependency('compress', 'RAR', 'https://www.rarlab.com/',
url_down='https://www.rarlab.com/download.htm',
url_down='https://www.rarlab.com/download.htm', help_option='-?',
roles=ToolDependencyRole(desc='Compress in RAR format')))
@ -107,6 +108,7 @@ class CompressOptions(BaseOptions):
check_output(cmd, stderr=STDOUT)
except FileNotFoundError:
logger.error('Missing `rar` command, install it')
logger.error(TRY_INSTALL_CHECK)
exit(MISSING_TOOL)
except CalledProcessError as e:
logger.error('Failed to invoke rar command, error {}'.format(e.returncode))

View File

@ -16,8 +16,9 @@ from . import log
logger = log.get_logger()
WARNING_MIX = "Avoid using it in conjunction with IBoM native filtering options"
RegDependency.register(ToolDependency('ibom', 'Interactive HTML BoM', URL_IBOM, url_down=URL_IBOM+'/releases',
is_kicad_plugin=True, command=CMD_IBOM, in_debian=False,
roles=ToolDependencyRole(version=(2, 4, 1, 3))))
command=CMD_IBOM, in_debian=False, no_cmd_line_version_old=True,
plugin_dirs=['InteractiveHtmlBom', 'InteractiveHtmlBom/InteractiveHtmlBom'],
roles=ToolDependencyRole(version=(2, 4, 1, 4))))
def check_tool():

View File

@ -23,7 +23,7 @@ from .kicad.config import KiConf
from .kicad.v5_sch import SchError
from .kicad.pcb import PCB
from .misc import (CMD_PCBNEW_PRINT_LAYERS, URL_PCBNEW_PRINT_LAYERS, PDF_PCB_PRINT, MISSING_TOOL, W_PDMASKFAIL,
KICAD5_SVG_SCALE, W_MISSTOOL, ToolDependency, ToolDependencyRole)
KICAD5_SVG_SCALE, W_MISSTOOL, ToolDependency, ToolDependencyRole, TRY_INSTALL_CHECK)
from .kiplot import check_script, exec_with_retry, add_extra_options
from .registrable import RegDependency
from .create_pdf import create_pdf_from_pages
@ -44,11 +44,14 @@ DRAWING_LAYERS = ['Dwgs.User', 'Cmts.User', 'Eco1.User', 'Eco2.User']
EXTRA_LAYERS = ['F.Fab', 'B.Fab', 'F.CrtYd', 'B.CrtYd']
RegDependency.register(ToolDependency('pcb_print', 'RSVG tools',
'https://cran.r-project.org/web/packages/rsvg/index.html', deb='librsvg2-bin',
command=SVG2PDF,
roles=ToolDependencyRole(desc='Create PDF, PNG, EPS and PS formats')))
RegDependency.register(ToolDependency('pcb_print', 'Ghostscript', 'https://www.ghostscript.com/',
url_down='https://github.com/ArtifexSoftware/ghostpdl-downloads/releases',
roles=ToolDependencyRole(desc='Create PS files')))
RegDependency.register(ToolDependency('pcb_print', 'lxml', is_python=True))
RegDependency.register(ToolDependency('pcb_print', 'ImageMagick', 'https://imagemagick.org/', command='convert',
roles=ToolDependencyRole(desc='Create monochrome prints')))
RegDependency.register(ToolDependency('pcb_print', 'LXML', is_python=True))
def _run_command(cmd):
@ -636,6 +639,7 @@ class PCB_PrintOptions(VariantOptions):
if monochrome:
if which('convert') is None:
logger.error('`convert` not installed. install `imagemagick` or equivalent')
logger.error(TRY_INSTALL_CHECK)
exit(MISSING_TOOL)
for img in self.last_worksheet.images:
with NamedTemporaryFile(mode='wb', suffix='.png', delete=False) as f:
@ -771,6 +775,7 @@ class PCB_PrintOptions(VariantOptions):
# Check PcbDraw is available
if which('pcbdraw') is None:
logger.error('`pcbdraw` not installed, needed for `realistic_solder_mask`')
logger.error(TRY_INSTALL_CHECK)
exit(MISSING_TOOL)
# Run PcbDraw to make the heavy work (find the Edge.Cuts path and create masks)
pcbdraw_file = os.path.join(temp_dir, out_file.replace('.svg', '-pcbdraw.svg'))
@ -864,10 +869,12 @@ class PCB_PrintOptions(VariantOptions):
def generate_output(self, output):
if self.format != 'SVG' and which(SVG2PDF) is None:
logger.error('`{}` not installed. Install `librsvg2-bin` or equivalent'.format(SVG2PDF))
logger.error(TRY_INSTALL_CHECK)
exit(MISSING_TOOL)
if self.format == 'PS' and which(PDF2PS) is None:
logger.error('`{}` not installed. '.format(PDF2PS))
logger.error('Install `librsvg2-bin` or equivalent')
logger.error(TRY_INSTALL_CHECK)
exit(MISSING_TOOL)
output_dir = os.path.dirname(output)
if self.keep_temporal_files:

View File

@ -9,7 +9,7 @@ from tempfile import NamedTemporaryFile
import subprocess
import shutil
from .misc import (PCBDRAW, PCBDRAW_ERR, URL_PCBDRAW, W_AMBLIST, W_UNRETOOL, W_USESVG2, W_USEIMAGICK, PCB_MAT_COLORS,
PCB_FINISH_COLORS, SOLDER_COLORS, SILK_COLORS, ToolDependency, ToolDependencyRole)
PCB_FINISH_COLORS, SOLDER_COLORS, SILK_COLORS, ToolDependency, ToolDependencyRole, TRY_INSTALL_CHECK)
from .kiplot import check_script
from .registrable import RegDependency
from .gs import GS
@ -24,9 +24,9 @@ CONVERT = 'convert'
# 0.9.0 implements KiCad 6 support
MIN_VERSION = '0.9.0'
RegDependency.register(ToolDependency('pcbdraw', 'RSVG tools', 'https://cran.r-project.org/web/packages/rsvg/index.html',
deb='librsvg2-bin',
deb='librsvg2-bin', command=SVG2PNG,
roles=ToolDependencyRole(desc='Create PNG and JPG images')))
RegDependency.register(ToolDependency('pcbdraw', 'ImageMagick', 'https://imagemagick.org/',
RegDependency.register(ToolDependency('pcbdraw', 'ImageMagick', 'https://imagemagick.org/', command='convert',
roles=ToolDependencyRole(desc='Create JPG images')))
RegDependency.register(ToolDependency('pcbdraw', 'PcbDraw', URL_PCBDRAW, url_down=URL_PCBDRAW+'/releases', in_debian=False,
roles=ToolDependencyRole(version=(0, 9, 0))))
@ -251,10 +251,12 @@ class PcbDrawOptions(VariantOptions):
if shutil.which(SVG2PNG) is None:
logger.warning(W_UNRETOOL + '`{}` not installed, using unreliable PNG/JPG conversion'.format(SVG2PNG))
logger.warning(W_USESVG2 + 'If you experiment problems install `librsvg2-bin` or equivalent')
logger.warning(W_USESVG2 + TRY_INSTALL_CHECK)
cmd.append(output)
elif shutil.which(CONVERT) is None:
logger.warning(W_UNRETOOL + '`{}` not installed, using unreliable PNG/JPG conversion'.format(CONVERT))
logger.warning(W_USEIMAGICK + 'If you experiment problems install `imagemagick` or equivalent')
logger.warning(W_USEIMAGICK + TRY_INSTALL_CHECK)
cmd.append(output)
else:
svg = _get_tmp_name('.svg')

View File

@ -24,7 +24,7 @@ QR_ECCS = {'low': QrCode.Ecc.LOW,
logger = log.get_logger()
TO_SEPARATE = {'kicad_pcb', 'general', 'title_block', 'layers', 'setup', 'pcbplotparams', 'net_class', 'module',
'kicad_sch', 'lib_symbols', 'symbol', 'sheet', 'sheet_instances', 'symbol_instances'}
RegDependency.register(ToolDependency('qr_lib', 'qrcodegen', is_python=True, roles=ToolDependencyRole()))
RegDependency.register(ToolDependency('qr_lib', 'QRCodeGen', is_python=True, roles=ToolDependencyRole()))
def is_symbol(name, sexp):

View File

@ -11,7 +11,8 @@ from shutil import which
from .gs import GS
from .misc import (UI_SMD, UI_VIRTUAL, MOD_THROUGH_HOLE, MOD_SMD, MOD_EXCLUDE_FROM_POS_FILES, PANDOC, MISSING_TOOL,
FAILED_EXECUTE, W_WRONGEXT, W_WRONGOAR, W_ECCLASST, W_MISSTOOL, ToolDependency, ToolDependencyRole)
FAILED_EXECUTE, W_WRONGEXT, W_WRONGOAR, W_ECCLASST, W_MISSTOOL, ToolDependency, ToolDependencyRole,
TRY_INSTALL_CHECK)
from .registrable import RegOutput, RegDependency
from .out_base import BaseOptions
from .error import KiPlotConfigurationError
@ -722,6 +723,7 @@ class ReportOptions(BaseOptions):
except FileNotFoundError:
logger.error("Unable to convert the report, `{}` must be installed.".format(PANDOC))
logger.error(PANDOC_INSTALL)
logger.error(TRY_INSTALL_CHECK)
exit(MISSING_TOOL)
except CalledProcessError as e:
logger.error('{} error: {}'.format(PANDOC, e.returncode))
@ -803,6 +805,7 @@ class Report(BaseOutput): # noqa: F821
def get_conf_examples(name, layers, templates):
if which(PANDOC) is None:
logger.warning((W_MISSTOOL+'Missing {} tool, disabling report in PDF format\n'+PANDOC_INSTALL).format(PANDOC))
logger.warning(W_MISSTOOL+TRY_INSTALL_CHECK)
pandoc = False
else:
pandoc = True

View File

@ -1,14 +1,17 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2021 Salvador E. Tropea
# Copyright (c) 2021 Instituto Nacional de Tecnología Industrial
# Copyright (c) 2021-2022 Salvador E. Tropea
# Copyright (c) 2021-2022 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
from .gs import GS
from .pre_any_replace import TagReplaceBase, Base_ReplaceOptions, Base_Replace
from .registrable import RegDependency
from .misc import git_dependency
from .macros import macros, document, pre_class # noqa: F401
from . import log
logger = log.get_logger()
RegDependency.register(git_dependency('pcb_replace'))
class TagReplacePCB(TagReplaceBase):

View File

@ -7,10 +7,13 @@ import os
from .gs import GS
from .kiplot import load_sch
from .pre_any_replace import TagReplaceBase, Base_ReplaceOptions, Base_Replace
from .registrable import RegDependency
from .misc import git_dependency
from .macros import macros, document, pre_class # noqa: F401
from . import log
logger = log.get_logger()
RegDependency.register(git_dependency('sch_replace'))
class TagReplaceSCH(TagReplaceBase):

View File

@ -8,14 +8,16 @@ import sys
import json
from subprocess import run, PIPE
from .error import KiPlotConfigurationError
from .misc import FAILED_EXECUTE, W_EMPTREP
from .misc import FAILED_EXECUTE, W_EMPTREP, git_dependency
from .optionable import Optionable
from .pre_base import BasePreFlight
from .gs import GS
from .registrable import RegDependency
from .macros import macros, document, pre_class # noqa: F401
from . import log
logger = log.get_logger()
RegDependency.register(git_dependency('set_text_variables'))
class KiCadVariable(Optionable):

View File

@ -170,8 +170,8 @@ class RegDependency(Registrable):
# Here we register some global dependencies
RegDependency.register(ToolDependency('global', 'colorama', is_python=True,
RegDependency.register(ToolDependency('global', 'Colorama', is_python=True,
roles=ToolDependencyRole(desc='get color messages in a portable way')))
RegDependency.register(ToolDependency('global', 'distutils', is_python=True))
RegDependency.register(ToolDependency('global', 'requests', is_python=True))
RegDependency.register(ToolDependency('global', 'PyYAML', is_python=True, deb='python3-yaml'))
RegDependency.register(ToolDependency('global', 'Distutils', is_python=True))
RegDependency.register(ToolDependency('global', 'Requests', is_python=True))
RegDependency.register(ToolDependency('global', 'PyYAML', is_python=True, deb='python3-yaml', module_name='yaml'))

View File

@ -18,7 +18,7 @@ setup(name='kibot',
url=__url__,
# Packages are marked using __init__.py
packages=find_packages(),
scripts=['src/kibot', 'src/kiplot'],
scripts=['src/kibot', 'src/kiplot', 'src/kibot-check'],
install_requires=__pypi_deps__,
include_package_data=True,
classifiers=['Development Status :: 5 - Production/Stable',

1028
src/kibot-check Executable file

File diff suppressed because it is too large Load Diff

410
src/kibot-check.in Executable file
View File

@ -0,0 +1,410 @@
#!/usr/bin/python3
# -*- 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)
#
# This is the installation checker, should help people to detect installation issues and install needed tools
import os
import re
import sys
import platform
import subprocess
import json
import importlib
from shutil import which
from contextlib import contextmanager
deps = '@json_dep@'
# Dirs to look for plugins
kicad_plugins_dirs = []
NOT_AVAIL = 'Not available'
UNKNOWN = '*UNKNOWN*'
CSI = '\033['
RED = CSI+str(31)+'m'
GREEN = CSI+str(32)+'m'
YELLOW = CSI+str(33)+'m'
YELLOW2 = CSI+str(93)+'m'
RESET = CSI+str(39)+'m'
BRIGHT = CSI+";1;4"+'m'
NORMAL = CSI+'0'+'m'
last_ok = False
is_x86 = is_64 = is_linux = False
ver_re = re.compile(r'(\d+)\.(\d+)(?:\.(\d+))?(?:[\.-](\d+))?')
def run_command(cmd, only_first_line=True, pre_ver_text=None, no_err_2=False):
global last_ok
try:
cmd_output = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
except FileNotFoundError as e:
last_ok = False
return NOT_AVAIL
except subprocess.CalledProcessError as e:
if e.returncode != 2 or not no_err_2:
print('Failed to run %s, error %d' % (cmd[0], e.returncode))
if e.output:
print('Output from command: '+e.output.decode())
last_ok = False
return UNKNOWN
res = cmd_output.decode().strip()
if only_first_line:
res = res.split('\n')[0]
pre_vers = (cmd[0]+' version ', cmd[0]+' ', pre_ver_text)
for pre_ver in pre_vers:
if pre_ver and res.startswith(pre_ver):
res = res[len(pre_ver):]
last_ok = True
return res
def simple_run_command(cmd):
res = run_command(cmd)
sev, ver = check_version(res, [{'mandatory': True, 'output': 'global', 'version': None}], no_ver=True)
return do_color(res, sev, version=ver)
def search_as_plugin(cmd, names):
""" If a command isn't in the path look for it in the KiCad plugins """
if which(cmd) is not None:
return cmd
for dir in kicad_plugins_dirs:
for name in names:
fname = os.path.join(dir, name, cmd)
if os.path.isfile(fname):
logger.debug('Using `{}` for `{}` ({})'.format(fname, cmd, name))
return fname
return cmd
@contextmanager
def hide_stderr():
""" Low level stderr suppression, used to hide KiCad bugs. """
newstderr = os.dup(2)
devnull = os.open('/dev/null', os.O_WRONLY)
os.dup2(devnull, 2)
os.close(devnull)
yield
os.dup2(newstderr, 2)
def do_int(v):
return int(v) if v is not None else 0
def check_version(version, roles, no_ver=False):
res = ver_re.search(version)
if res:
ver = list(map(do_int, res.groups()))
else:
ver = [0, 0, 0]
not_avail = version == NOT_AVAIL or version == UNKNOWN
severity = 0
for r in roles:
mandatory = r['mandatory']
glb = r['output'] == 'global'
this_sever = 0
if not_avail or (r['version'] and ver < r['version']):
if mandatory:
this_sever = 4 if glb else 3
else:
this_sever = 2 if glb else 1
severity = max(severity, this_sever)
r['sev'] = this_sever
return severity, ver
def sev2color(severity):
if severity == 4:
return RED
elif severity == 3:
return YELLOW2
elif severity:
return YELLOW
else:
return GREEN
def do_color(msg, severity, version=None):
if version is not None and version != [0, 0, 0]:
if len(version) == 4 and version[3] == 0:
version = version[:-1]
ver_str = '.'.join(map(str, version))
if ver_str != msg:
msg = ver_str+' ('+msg+')'
return sev2color(severity)+msg+RESET
def error(msg):
print(sev2color(4)+'**> '+msg+RESET)
def do_bright(msg):
return BRIGHT+msg+NORMAL
def global2human(name):
return '`'+name+'`' if name != 'global' else 'general use'
def show_roles(roles):
needed = []
optional = []
for r in roles:
if r['mandatory']:
needed.append(r)
else:
optional.append(r)
r['output'] = global2human(r['output'])
if needed:
if len(needed) == 1:
color = sev2color(needed[0]['sev'])
name = needed[0]['output']
if name == 'general use':
print(color+' - Mandatory')
else:
print(color+' - Mandatory for '+name)
else:
need_s = sorted(needed, key=lambda x: x['output'])
print(RESET+' - Mandatory for: '+', '.join([sev2color(f['sev'])+f['output']+RESET for f in need_s]))
if optional:
if len(optional) == 1:
o = optional[0]
desc = o['desc'][0].lower()+o['desc'][1:]
print(sev2color(o['sev'])+' - Optional to {} for {}'.format(desc, o['output']))
else:
print(RESET+' - Optional to:')
for o in optional:
ver = ''
if o['version']:
ver = ' (v'+'.'.join(map(str, o['version']))+')'
print(sev2color(o['sev'])+' - {} for {}{}'.format(o['desc'], o['output'], ver))
def python_module(severity, name, deb_package, roles):
if not severity:
return
print(sev2color(severity)+'* Python module `{}` not installed or too old'.format(name))
if debian_support:
if deb_package is None:
deb_package = 'python3-'+name
print(' Install the `{0}` package, i.e.: `sudo apt-get install {0}`'.format(deb_package))
elif pip_ok:
print(' run `{} install {}` as root,'.format(pip_command, name))
print(' or run `{} install --user {}` as a regular user'.format(pip_command, name))
else:
print(' Install the Package Installer for Python (pip) and run this script again')
show_roles(roles)
print(RESET)
def binary_tool(severity, name, url, url_down, deb_package, deb, extra_deb, roles):
if not severity:
return
print(sev2color(severity)+'* {} not installed or too old'.format(name))
if deb and debian_support:
if deb_package is None:
deb_package = name.lower()
print(' Install the `{0}` package, i.e.: `sudo apt-get install {0}`'.format(deb_package))
if extra_deb:
print(' You should also install the following packages: '+', '.join(extra_deb))
else:
print(' Visit: '+url)
if url_down:
print(' Download it from: '+url_down)
show_roles(roles)
print(RESET)
# ######################################################################################################################
# Core tools
# ######################################################################################################################
print('KiBot installation checker\n')
print(do_bright('Core:'))
# Operating system
system = platform.system()
if system == 'Linux':
linux_version = simple_run_command(['uname', '-a'])
print('Linux: '+linux_version)
os_ok = True
is_x86 = 'x86' in linux_version
is_64 = ('x86_64' in linux_version) or ('amd64' in linux_version)
is_linux = True
else:
print(system)
os_ok = False
# Python version
if sys.version_info >= (3, 6):
py_ok = True
sev = 0
else:
py_ok = False
sev = 4
print('Python: '+do_color(sys.version.replace('\n', ' '), sev))
# KiCad
try:
import pcbnew
kicad_ok = True
# Fill the plug-in locations
# TODO: Windows? MacOSX?
kicad_share_path = '/usr/share/kicad'
if hasattr(pcbnew, 'GetKicadConfigPath'):
with hide_stderr():
kicad_conf_path = pcbnew.GetKicadConfigPath()
elif hasattr(pcbnew, 'GetSettingsManager'):
kicad_conf_path = pcbnew.GetSettingsManager().GetUserSettingsPath()
else:
kicad_conf_path = None
# /usr/share/kicad/*
kicad_plugins_dirs.append(os.path.join(kicad_share_path, 'scripting'))
kicad_plugins_dirs.append(os.path.join(kicad_share_path, 'scripting', 'plugins'))
# ~/.config/kicad/*
if kicad_conf_path:
kicad_plugins_dirs.append(os.path.join(kicad_conf_path, 'scripting'))
kicad_plugins_dirs.append(os.path.join(kicad_conf_path, 'scripting', 'plugins'))
# ~/.kicad_plugins and ~/.kicad
if 'HOME' in os.environ:
home = os.environ['HOME']
kicad_plugins_dirs.append(os.path.join(home, '.kicad_plugins'))
kicad_plugins_dirs.append(os.path.join(home, '.kicad', 'scripting'))
kicad_plugins_dirs.append(os.path.join(home, '.kicad', 'scripting', 'plugins'))
except FileNotFoundError:
kicad_ok = False
kicad_version = (0, 0, 0)
if kicad_ok:
try:
version = pcbnew.GetBuildVersion()
# KiCad version
m = re.search(r'(\d+)\.(\d+)\.(\d+)', version)
if m is None:
error("Unable to detect KiCad version, got: `{}`".format(version))
else:
kicad_version = (int(m.group(1)), int(m.group(2)), int(m.group(3)))
except:
version = 'Older than 5.1.6'
else:
version = NOT_AVAIL
if kicad_version >= (5, 1, 6) and kicad_version < (6, 99):
sev = 0
else:
sev = 4
print('KiCad: '+do_color(version, sev))
# KiBot version
try:
from kibot.__main__ import __version__
kibot_ok = True
sev = 0
except:
__version__ = NOT_AVAIL
kibot_ok = False
sev = 4
print('Kibot: '+do_color(__version__, sev))
if kibot_ok and which('kibot') is None:
print(sev2color(4)+'* KiBot is installed but not available in your PATH')
import kibot
if '/lib/' in kibot.__file__:
v = re.sub(r'\/lib\/.*', '/bin/kibot', kibot.__file__)
if os.path.isfile(v):
print(' Try adding `{}` to your PATH'.format(v[:-5]))
print(' I.e.: export PATH=$PATH:'+v[:-5])
sys.exit(1)
dependencies = json.loads(deps)
print(do_bright('\nModules:'))
for name, d in dependencies.items():
if not d['is_python']:
continue
try:
mod = importlib.import_module(d['module_name'])
if hasattr(mod, '__version__'):
version = mod.__version__
else:
version = 'Ok'
except:
version = NOT_AVAIL
sev, ver = check_version(version, d['roles'])
d['sev'] = sev
print(name+': '+do_color(version, sev, version=ver))
print(do_bright('\nTools:'))
for name, d in dependencies.items():
if d['is_python']:
continue
command = d['command']
if d['is_kicad_plugin']:
command = search_as_plugin(command, d['plugin_dirs'])
if d['no_cmd_line_version']:
version = 'Ok ({})'.format(command) if which(command) is not None else NOT_AVAIL
else:
version = run_command([command, d['help_option']], no_err_2=d['no_cmd_line_version_old'])
sev, ver = check_version(version, d['roles'])
d['sev'] = sev
print(name+': '+do_color(version, sev, version=ver))
# ######################################################################################################################
# Recommendations
# ######################################################################################################################
print()
debian_support = False
if which('apt-get'):
debian_support = True
pip_ok = False
if which('pip3'):
pip_ok = True
pip_command = 'pip3'
elif which('pip'):
pip_ok = True
pip_command = 'pip'
if not os_ok:
print(sev2color(4)+'* KiBot is currently tested under Linux')
if system == 'Darwin':
print(' MacOSX should be supported for KiCad 6.x')
elif system == 'Windows':
print(' Windows may work with some limitations for KiCad 6.x')
print(' Consider using a docker image, Windows docker can run Linux images (using virtualization)')
else:
print(' What OS are you using? Is KiCad available for it?')
print(' Please consult: https://github.com/INTI-CMNB/KiBot/issues')
print(RESET)
if not py_ok:
print(sev2color(4)+'* Install Python 3.6 or newer')
print(RESET)
if not kicad_ok:
print(sev2color(4)+'* Install KiCad 5.1.6 or newer')
if debian_support:
print(' Try `apt-get install kicad` as root')
else:
print(' Download it from: https://www.kicad.org/download/')
print(RESET)
if not kibot_ok:
print(sev2color(4)+'* Install KiBot!')
if debian_support:
print(' Follow the instructions here: https://set-soft.github.io/debian/')
elif pip_ok:
print(' run `{} install --no-compile kibot` as root,'.format(pip_command))
print(' or run `{} install --user --no-compile kibot` as a regular user'.format(pip_command))
else:
print(' Install the Package Installer for Python (pip) and run this script again')
print(RESET)
for name, d in dependencies.items():
if d['is_python']:
python_module(d['sev'], d['pypi_name'], d['deb_package'], d['roles'])
else:
binary_tool(d['sev'], d['name'], d['url'], d['url_down'], d['deb_package'], d['in_debian'], d['extra_deb'],
d['roles'])
labels = ('ok', 'optional for an output', 'optional for general use', 'mandatory for an output', 'mandatory for general use')
text = ', '.join([sev2color(c)+l+RESET for c, l in enumerate(labels)])
print(do_bright('\nColor reference:')+' '+text)
print('\nDid this help? Please consider commenting it on https://github.com/INTI-CMNB/KiBot/issues/200')