├── .appveyor.yml ├── .ci ├── scripts │ ├── Dockerfile │ ├── appveyor_env.ps1 │ ├── docker_build.sh │ ├── docker_entry_point.sh │ ├── run_tests.py │ ├── setup_env.ps1 │ ├── setup_ghdl.ps1 │ ├── setup_msim.ps1 │ ├── test_ghdl.ps1 │ └── test_msim.ps1 └── test_support │ ├── test_builders │ ├── no_messages.sv │ ├── no_messages.v │ ├── no_messages.vhd │ └── source_with_error.vhd │ └── test_project │ ├── another_library │ └── foo.vhd │ ├── basic_library │ ├── clk_en_generator.vhd │ ├── clock_divider.vhd │ ├── package_with_constants.vhd │ ├── package_with_functions.vhd │ ├── two_entities_one_file.vhd │ ├── use_entity_a_and_b.vhd │ └── very_common_pkg.vhd │ ├── config.json │ ├── ghdl.prj │ ├── msim.prj │ ├── verilog │ ├── parity.sv │ └── parity.v │ ├── vimhdl.prj │ └── xvhdl.prj ├── .coveragerc ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE.md └── workflows │ └── unit_tests.yml ├── .gitignore ├── .pylintrc ├── LICENSE ├── MANIFEST.in ├── README.md ├── hdl_checker ├── __init__.py ├── __main__.py ├── _version.py ├── builder_utils.py ├── builders │ ├── __init__.py │ ├── base_builder.py │ ├── fallback.py │ ├── ghdl.py │ ├── msim.py │ └── xvhdl.py ├── config_generators │ ├── __init__.py │ ├── base_generator.py │ └── simple_finder.py ├── core.py ├── database.py ├── diagnostics.py ├── exceptions.py ├── handlers.py ├── lsp.py ├── parser_utils.py ├── parsers │ ├── __init__.py │ ├── base_parser.py │ ├── config_parser.py │ ├── elements │ │ ├── __init__.py │ │ ├── dependency_spec.py │ │ ├── design_unit.py │ │ ├── identifier.py │ │ └── parsed_element.py │ ├── verilog_parser.py │ └── vhdl_parser.py ├── path.py ├── serialization.py ├── server.py ├── static_check.py ├── tests │ ├── __init__.py │ ├── test_base_server.py │ ├── test_builder_utils.py │ ├── test_builders.py │ ├── test_config_generator.py │ ├── test_config_parser.py │ ├── test_database.py │ ├── test_lsp.py │ ├── test_lsp_utils.py │ ├── test_misc.py │ ├── test_parser_utils.py │ ├── test_server.py │ ├── test_server_handlers.py │ ├── test_static_check.py │ ├── test_verilog_parser.py │ └── test_vhdl_parser.py ├── types.py └── utils.py ├── run_tests.sh ├── setup.cfg ├── setup.py ├── tox.ini ├── unittest.cfg └── versioneer.py /.appveyor.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # ---------------------------------# 3 | # general configuration # 4 | # ---------------------------------# 5 | 6 | # version format 7 | version: '{build}' 8 | 9 | # branches to build 10 | branches: 11 | # blacklist 12 | except: 13 | - gh-pages 14 | 15 | # Do not build on tags (GitHub only) 16 | skip_tags: true 17 | 18 | # Maximum number of concurrent jobs for the project 19 | max_jobs: 3 20 | 21 | 22 | artifacts: 23 | - path: .tox 24 | name: Tox runtime 25 | 26 | # ---------------------------------# 27 | # environment configuration # 28 | # ---------------------------------# 29 | 30 | # Operating system (build VM template) 31 | image: Visual Studio 2017 32 | 33 | # clone directory 34 | clone_folder: c:\projects\hdl_checker 35 | 36 | # fetch repository as zip archive 37 | shallow_clone: false 38 | 39 | # set clone depth 40 | clone_depth: 5 41 | 42 | # environment variables 43 | environment: 44 | GHDL_URL: http://pilotfiber.dl.sourceforge.net/project/ghdl-updates/Builds/ghdl-0.33/ghdl-0.33-win32.zip 45 | MSIM_URL: http://download.altera.com/akdlm/software/acdsinst/15.1/185/ib_installers/ModelSimSetup-15.1.0.185-windows.exe 46 | ARCH: 32 47 | 48 | matrix: 49 | # - PYTHON_VERSION: 37 50 | # PYTHON: "C:\\Python37" 51 | # BUILDER: ghdl 52 | 53 | # - PYTHON_VERSION: 37 54 | # PYTHON: "C:\\Python37" 55 | # BUILDER: msim 56 | 57 | # - PYTHON_VERSION: 37 58 | # PYTHON: "C:\\Python37" 59 | # TOXENV: py27-windows 60 | 61 | - PYTHON_VERSION: 37 62 | PYTHON: "C:\\Python37" 63 | TOXENV: py37-windows 64 | 65 | # scripts that run after cloning repository 66 | install: 67 | - ps: . "$env:APPVEYOR_BUILD_FOLDER\\.ci\\scripts\\setup_env.ps1" 68 | # We need wheel installed to build wheels 69 | - "%PYTHON%\\python.exe -m pip install --upgrade pip" 70 | - "%PYTHON%\\python.exe -m pip install wheel tox" 71 | 72 | - ps: . "$env:APPVEYOR_BUILD_FOLDER\\.ci\\scripts\\appveyor_env.ps1" 73 | # - ps: if ($env:BUILDER "msim") { . "$env:APPVEYOR_BUILD_FOLDER\\.ci\\scripts\\setup_msim.ps1" } 74 | # - ps: if ($env:BUILDER "ghdl") { . "$env:APPVEYOR_BUILD_FOLDER\\.ci\\scripts\\setup_ghdl.ps1" } 75 | - python --version 76 | 77 | test_script: 78 | - tox -- -v 79 | after_test: 80 | - 7z a tox.zip ".tox" 81 | - appveyor PushArtifact tox.zip 82 | - ps: | 83 | $env:PATH = 'C:\msys64\usr\bin;' + $env:PATH 84 | Invoke-WebRequest -Uri 'https://codecov.io/bash' -OutFile codecov.sh 85 | bash codecov.sh -f "coverage.xml" -U "-s" -A "-s" 86 | 87 | # We won't build or run tests from here 88 | build: off 89 | 90 | on_failure: 91 | - 7z a tox.zip ".tox" 92 | - appveyor PushArtifact tox.zip 93 | -------------------------------------------------------------------------------- /.ci/scripts/Dockerfile: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | 18 | FROM suoto/xsim:2018.3 19 | 20 | ENV BUILDERS /builders 21 | 22 | ENV DEBIAN_FRONTEND noninteractive 23 | RUN dpkg --add-architecture i386 24 | 25 | ENV PACKAGES="ca-certificates \ 26 | g++-multilib \ 27 | gcc \ 28 | gcc-multilib \ 29 | git \ 30 | gnat \ 31 | lib32gcc1 \ 32 | lib32stdc++6 \ 33 | lib32z1 \ 34 | libtcl8.6 \ 35 | make \ 36 | python2.7 \ 37 | python2.7-dev \ 38 | python3-pip \ 39 | python3.6 \ 40 | python3.6-dev \ 41 | python3.7 \ 42 | python3.7-dev \ 43 | python3.8 \ 44 | python3.8-dev \ 45 | wget \ 46 | zlib1g-dev" 47 | 48 | RUN apt-get update -qq && \ 49 | apt-get install -qq $PACKAGES && \ 50 | apt-get clean && \ 51 | rm -rf /var/lib/apt/lists/* 52 | 53 | RUN pip3 install tox 54 | RUN pip3 install coverage==4.1 55 | 56 | WORKDIR $BUILDERS 57 | 58 | # Build GHDL from source 59 | WORKDIR /tmp/ 60 | RUN git clone --depth=1 --branch=v0.37.0 https://github.com/ghdl/ghdl 61 | WORKDIR /tmp/ghdl 62 | WORKDIR /tmp/ghdl/build 63 | 64 | RUN ../configure --prefix=$BUILDERS/ghdl && \ 65 | make -j && \ 66 | make install 67 | 68 | RUN apt-get purge -qq make zlib1g-dev && \ 69 | apt-get clean && \ 70 | apt-get autoclean 71 | RUN rm -rf /tmp/ghdl 72 | 73 | RUN $BUILDERS/ghdl/bin/ghdl --version 74 | 75 | # ModelSim will be copied 76 | COPY msim $BUILDERS/msim 77 | 78 | # Test installations 79 | RUN "$BUILDERS/ghdl/bin/ghdl" --version 80 | RUN "$BUILDERS/msim/modelsim_ase/linuxaloem/vcom" -version 81 | RUN "/xsim/bin/xvhdl" -version 82 | 83 | VOLUME /hdl_checker 84 | WORKDIR /hdl_checker 85 | -------------------------------------------------------------------------------- /.ci/scripts/appveyor_env.ps1: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | 18 | write-host "Creating AppVeyor-like environment variables" 19 | 20 | $env:CI_WORK_PATH="$env:USERPROFILE\\ci" 21 | 22 | if ($env:APPVEYOR -ne "True") { 23 | $env:APPVEYOR_BUILD_FOLDER=$(get-location) 24 | $env:PATH="C:\Program Files\7-zip;$env:PATH" 25 | } 26 | 27 | if ($env:BUILDER -eq "msim") { 28 | $env:BUILDER_PATH="$env:CI_WORK_PATH\\modelsim_ase\\win32aloem" 29 | } elseif ($env:BUILDER -eq "ghdl") { 30 | # $env:INSTALL_DIR="$env:CI_WORK_PATH\\ghdl-0.31-mcode-win32" 31 | $env:INSTALL_DIR="$env:CI_WORK_PATH\\ghdl-0.33" 32 | $env:BUILDER_PATH="$env:INSTALL_DIR\\bin" 33 | } 34 | 35 | $env:CACHE_PATH="$env:CI_WORK_PATH\\cache" 36 | 37 | if (!(Test-Path "$env:CI_WORK_PATH")) { 38 | cmd /c "mkdir `"$env:CI_WORK_PATH`"" 39 | } 40 | 41 | if (!(Test-Path "$env:CACHE_PATH")) { 42 | cmd /c "mkdir `"$env:CACHE_PATH`"" 43 | } 44 | 45 | "CACHE_PATH $env:CACHE_PATH" 46 | "ARCH $env:ARCH" 47 | "CI_WORK_PATH $env:CI_WORK_PATH" 48 | "APPVEYOR $env:APPVEYOR" 49 | "APPVEYOR_BUILD_FOLDER $env:APPVEYOR_BUILD_FOLDER" 50 | "PATH $env:PATH" 51 | "BUILDER $env:BUILDER" 52 | "CACHE_PATH $env:CACHE_PATH" 53 | "INSTALL_DIR $env:INSTALL_DIR" 54 | -------------------------------------------------------------------------------- /.ci/scripts/docker_build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # This file is part of HDL Checker. 3 | # 4 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 5 | # 6 | # HDL Checker is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # HDL Checker is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with HDL Checker. If not, see . 18 | 19 | set -xe 20 | 21 | DOCKER_TAG="${DOCKER_TAG:-latest}" 22 | PATH_TO_THIS_SCRIPT=$(readlink -f "$(dirname "$0")") 23 | DOCKERFILE=$PATH_TO_THIS_SCRIPT/Dockerfile 24 | CONTEXT=$HOME/context 25 | DOWNLOAD_DIR=$HOME/Downloads 26 | 27 | mkdir -p "$CONTEXT" 28 | 29 | # $1 Test filename 30 | # $2 URL 31 | function download_if_needed { 32 | filename=$1 33 | url=$2 34 | 35 | if [ ! -f "$filename" ]; then 36 | wget "$url" -O "$filename" 37 | fi 38 | } 39 | 40 | function setup_msim { 41 | 42 | pushd "$CONTEXT" || exit 1 43 | 44 | URL_MAIN=http://download.altera.com/akdlm/software/acdsinst/19.2/57/ib_installers/ModelSimProSetup-19.2.0.57-linux.run 45 | URL_PART_2=http://download.altera.com/akdlm/software/acdsinst/19.2/57/ib_installers/modelsim-part2-19.2.0.57-linux.qdz 46 | 47 | installer=$(basename $URL_MAIN) 48 | 49 | if [ ! -f "$CONTEXT/msim/modelsim_ase/linuxaloem/vsim" ]; then 50 | 51 | download_if_needed "$DOWNLOAD_DIR/$installer" $URL_MAIN 52 | download_if_needed "$DOWNLOAD_DIR/$(basename $URL_PART_2)" $URL_PART_2 53 | 54 | chmod +x "$DOWNLOAD_DIR/$installer" 55 | "$DOWNLOAD_DIR/$installer" --mode unattended \ 56 | --modelsim_edition modelsim_ase \ 57 | --accept_eula 1 \ 58 | --installdir "$CONTEXT"/msim 59 | 60 | rm -rf "$CONTEXT"/msim/modelsim_ase/altera 61 | fi 62 | 63 | popd || exit 1 64 | 65 | } 66 | 67 | setup_msim 68 | 69 | "$CONTEXT"/msim/modelsim_ase/linuxaloem/vsim -version 70 | 71 | docker build -t suoto/hdl_checker_test:"$DOCKER_TAG" -f "$DOCKERFILE" "$CONTEXT" 72 | 73 | -------------------------------------------------------------------------------- /.ci/scripts/docker_entry_point.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # This file is part of HDL Checker. 3 | # 4 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 5 | # 6 | # HDL Checker is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # HDL Checker is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with HDL Checker. If not, see . 18 | 19 | set -e 20 | 21 | # Mimic the username, user ID and group ID of the env outside the container to 22 | # avoid permission issues 23 | 24 | USERNAME="${USERNAME:-user}" 25 | 26 | addgroup "$USERNAME" --gid "$GROUP_ID" > /dev/null 2>&1 27 | 28 | adduser --disabled-password \ 29 | --gid "$GROUP_ID" \ 30 | --uid "$USER_ID" \ 31 | --home "/home/$USERNAME" "$USERNAME" > /dev/null 2>&1 32 | 33 | ln -s /builders "/home/$USERNAME/builders" 34 | 35 | su -l "$USERNAME" -c " \ 36 | cd /hdl_checker && \ 37 | tox ${TOX_ARGS[*]} && \ 38 | coverage combine && \ 39 | coverage xml && \ 40 | coverage report && \ 41 | coverage html" 42 | -------------------------------------------------------------------------------- /.ci/scripts/run_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of HDL Checker. 3 | # 4 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 5 | # 6 | # HDL Checker is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # HDL Checker is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with HDL Checker. If not, see . 18 | # PYTHON_ARGCOMPLETE_OK 19 | from __future__ import print_function 20 | 21 | import argparse 22 | import logging 23 | import os 24 | import os.path as p 25 | import sys 26 | 27 | import coverage # type: ignore 28 | 29 | import nose2 # type: ignore 30 | 31 | try: # Python 3.x 32 | import unittest.mock as mock # pylint: disable=import-error, no-name-in-module 33 | except ImportError: # Python 2.x 34 | import mock # type: ignore 35 | 36 | try: 37 | import argcomplete # type: ignore 38 | 39 | _HAS_ARGCOMPLETE = True 40 | except ImportError: # pragma: no cover 41 | _HAS_ARGCOMPLETE = False 42 | 43 | _CI = os.environ.get("CI", None) is not None 44 | _APPVEYOR = os.environ.get("APPVEYOR", None) is not None 45 | _TRAVIS = os.environ.get("TRAVIS", None) is not None 46 | _ON_WINDOWS = sys.platform == "win32" 47 | BASE_PATH = p.abspath(p.join(p.dirname(__file__))) 48 | 49 | _logger = logging.getLogger(__name__) 50 | 51 | 52 | def _shell(cmd): 53 | _logger.info("$ %s", cmd) 54 | for line in os.popen(cmd).read().split("\n"): 55 | if line and not line.isspace(): 56 | _logger.info("> %s", line) 57 | 58 | 59 | def _clear(): 60 | "Clears the current repo and submodules" 61 | for cmd in ("git clean -fdx", "git submodule foreach --recursive git clean -fdx"): 62 | _shell(cmd) 63 | 64 | 65 | def _setupLogging(stream, level): # pragma: no cover 66 | "Setup logging according to the command line parameters" 67 | color = False 68 | 69 | if stream is sys.stdout: 70 | 71 | class Stream(object): 72 | def isatty(self): 73 | return True 74 | 75 | def write(self, *args, **kwargs): 76 | return sys.stdout.write(*args, **kwargs) 77 | 78 | _stream = Stream() 79 | else: 80 | _stream = stream 81 | 82 | color = False 83 | try: 84 | color = _stream.isatty() 85 | except AttributeError: 86 | pass 87 | 88 | if color: 89 | try: 90 | from rainbow_logging_handler import ( # type: ignore 91 | RainbowLoggingHandler, 92 | ) 93 | 94 | color = True 95 | except ImportError: 96 | color = False 97 | 98 | formatter = logging.Formatter( 99 | "%(levelname)-8s | %(asctime)s | %(threadName)-10s | " 100 | + "%(name)s @ %(funcName)s():%(lineno)d " 101 | + "|\t%(message)s", 102 | datefmt="%H:%M:%S", 103 | ) 104 | 105 | if color: 106 | # RainbowLoggingHandler._fmt = '[%(asctime)s] %(threadName)s %(name)s %(funcName)s():%(lineno)d\t%(message)s' 107 | rainbow_stream_handler = RainbowLoggingHandler(_stream) 108 | rainbow_stream_handler.setFormatter(formatter) 109 | 110 | logging.root.addHandler(rainbow_stream_handler) 111 | logging.root.setLevel(level) 112 | else: 113 | handler = logging.StreamHandler(_stream) 114 | handler.formatter = formatter 115 | 116 | logging.root.addHandler(handler) 117 | logging.root.setLevel(level) 118 | 119 | 120 | def _parseArguments(): 121 | parser = argparse.ArgumentParser() 122 | 123 | # Options 124 | parser.add_argument( 125 | "tests", action="append", nargs="*", help="Test names or files to be run" 126 | ) 127 | parser.add_argument("--fail-fast", "-F", action="store_true") 128 | 129 | parser.add_argument("--debugger", "-D", action="store_true") 130 | 131 | parser.add_argument( 132 | "--verbose", "-v", action="append_const", const="-v", default=[] 133 | ) 134 | 135 | parser.add_argument( 136 | "--log-file", 137 | action="store", 138 | default=p.join(os.environ["TOX_ENV_DIR"], "log", "tests.log"), 139 | ) 140 | 141 | parser.add_argument( 142 | "--log-level", 143 | action="store", 144 | default="INFO", 145 | choices=("CRITICAL", "DEBUG", "ERROR", "INFO", "WARNING"), 146 | ) 147 | 148 | parser.add_argument("--log-to-stdout", action="store_true") 149 | 150 | if _HAS_ARGCOMPLETE: # pragma: no cover 151 | argcomplete.autocomplete(parser) 152 | 153 | args = parser.parse_args() 154 | args.log_level = str(args.log_level).upper() 155 | 156 | if args.tests: 157 | test_list = [source for sublist in args.tests for source in sublist] 158 | 159 | args.tests = [] 160 | 161 | for test_name_or_file in test_list: 162 | if p.exists(test_name_or_file): 163 | test_name = str(test_name_or_file).replace("/", ".") 164 | test_name = str(test_name).replace(".py", "") 165 | print("Argument '%s' converted to '%s'" % (test_name_or_file, test_name)) 166 | else: 167 | test_name = test_name_or_file 168 | 169 | args.tests.append(test_name) 170 | 171 | return args 172 | 173 | 174 | def _getNoseCommandLineArgs(args): 175 | argv = [] 176 | 177 | argv += args.verbose 178 | if args.debugger: 179 | argv += ["--debugger"] 180 | if args.fail_fast: 181 | argv += [ 182 | "--fail-fast", 183 | ] 184 | # Need at least 2 verbose levels for this to work! 185 | argv += ["--verbose"] * (2 - len(args.verbose)) 186 | if not args.log_to_stdout: 187 | argv += ["--log-capture"] 188 | 189 | return argv 190 | 191 | 192 | def runTestsForEnv(args): 193 | nose_base_args = _getNoseCommandLineArgs(args) 194 | nose_args = list(nose_base_args) 195 | 196 | if args.tests: 197 | nose_args += args.tests 198 | 199 | test_env = os.environ.copy() 200 | 201 | test_env.update({"SERVER_LOG_LEVEL": args.log_level}) 202 | 203 | home = p.join(os.environ["TOX_ENV_DIR"], "tmp", "home") 204 | os.makedirs(home) 205 | 206 | if not _ON_WINDOWS: 207 | test_env.update({"HOME": home}) 208 | else: 209 | test_env.update({"LOCALAPPDATA": home}) 210 | 211 | _logger.info("nose2 args: %s", nose_args) 212 | 213 | with mock.patch.dict("os.environ", test_env): 214 | successful = nose2.discover(exit=False, argv=nose_args).result.wasSuccessful() 215 | 216 | return successful 217 | 218 | 219 | def main(): 220 | args = _parseArguments() 221 | if args.log_to_stdout: 222 | _setupLogging(sys.stdout, args.log_level) 223 | 224 | _logger.info("Arguments: %s", args) 225 | 226 | logging.getLogger("nose2").setLevel(logging.FATAL) 227 | logging.getLogger("vunit").setLevel(logging.ERROR) 228 | logging.getLogger("requests").setLevel(logging.WARNING) 229 | 230 | logging.getLogger("pygls").setLevel(logging.INFO) 231 | logging.getLogger("pygls.protocol").setLevel(logging.INFO) 232 | logging.getLogger("pygls.server").setLevel(logging.INFO) 233 | logging.getLogger("pygls.feature_manager").setLevel(logging.INFO) 234 | 235 | file_handler = logging.FileHandler(args.log_file) 236 | log_format = ( 237 | "%(levelname)-7s | %(asctime)s | " 238 | + "%(name)s @ %(funcName)s():%(lineno)d %(threadName)s |\t%(message)s" 239 | ) 240 | file_handler.formatter = logging.Formatter(log_format, datefmt="%H:%M:%S") 241 | logging.root.addHandler(file_handler) 242 | logging.root.setLevel(args.log_level) 243 | 244 | cov = coverage.Coverage(config_file=".coveragerc") 245 | cov.start() 246 | 247 | passed = runTestsForEnv(args) 248 | 249 | cov.stop() 250 | cov.save() 251 | 252 | if not passed: 253 | _logger.warning("Some tests failed!") 254 | print("Some tests failed!") 255 | 256 | return 0 if passed else 1 257 | 258 | 259 | if __name__ == "__main__": 260 | sys.exit(main()) 261 | -------------------------------------------------------------------------------- /.ci/scripts/setup_env.ps1: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | 18 | write-host "Configured builder is $env:BUILDER" 19 | 20 | if ($env:ARCH -eq 32) { 21 | $env:python_path = "C:\Python$env:PYTHON_VERSION" 22 | } else { 23 | $env:python_path = "C:\Python$env:PYTHON_VERSION-x64" 24 | } 25 | 26 | $env:PATH="$env:python_path;$env:python_path\Scripts;$env:PATH" 27 | 28 | Start-Process "git" -RedirectStandardError git.log -Wait -NoNewWindow -ArgumentList ` 29 | "submodule update --init --recursive" 30 | get-content git.log 31 | 32 | if ($env:APPVEYOR -eq "True") { 33 | appveyor DownloadFile https://bootstrap.pypa.io/get-pip.py 34 | if (!$?) {write-error "Error while downloading get-pip.py, exiting"; exit -1} 35 | "$env:PYTHON\\python.exe get-pip.py" 36 | } 37 | 38 | 39 | if ("$env:BUILDER" -eq "msim") { 40 | echo "Installing MSIM" 41 | . "$env:APPVEYOR_BUILD_FOLDER\\.ci\\scripts\\setup_msim.ps1" 42 | if (!$?) {write-error "Error while installing ModelSim"; exit -1} 43 | } elseif ("$env:BUILDER" -eq "ghdl") { 44 | echo "Installing GHDL" 45 | . "$env:APPVEYOR_BUILD_FOLDER\\.ci\\scripts\\setup_ghdl.ps1" 46 | if (!$?) {write-error "Error while installing GHDL"; exit -1} 47 | } else { 48 | echo "No builder selected" 49 | } 50 | 51 | write-host "Arch is $env:ARCH, Python selected is $env:python_path" 52 | 53 | -------------------------------------------------------------------------------- /.ci/scripts/setup_ghdl.ps1: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | 18 | write-host "Setting up GHDL..." 19 | 20 | $env:GHDL_PREFIX="$env:INSTALL_DIR\\lib" 21 | 22 | if (!(Test-Path "$env:CACHE_PATH\\ghdl.zip")) { 23 | write-host "Downloading $env:BUILDER_NAME from $env:GHDL_URL to $env:CACHE_PATH\\ghdl.zip" 24 | 25 | do { 26 | if ($env:APPVEYOR -eq "True") { 27 | "appveyor DownloadFile `"$env:GHDL_URL`" -filename `"$env:CACHE_PATH\\ghdl.zip`"" 28 | cmd /c "appveyor DownloadFile `"$env:GHDL_URL`" -filename `"$env:CACHE_PATH\\ghdl.zip`"" 29 | } else { 30 | "curl -fsSL `"$env:GHDL_URL`" --output `"$env:CACHE_PATH\\ghdl.zip`"" 31 | curl -fsSL "$env:GHDL_URL" --output "$env:CACHE_PATH\\ghdl.zip" 32 | } 33 | $failed = 0 34 | if (!$?) { 35 | write-error "Something went wrong, retrying" 36 | exit -1 37 | $failed = 1 38 | } 39 | } while ($failed) 40 | write-host "Download finished" 41 | } 42 | 43 | if (!(Test-Path "$env:BUILDER_PATH")) { 44 | write-host "Installing $env:BUILDER_NAME to $env:CI_WORK_PATH" 45 | cmd /c "7z x `"$env:CACHE_PATH\\ghdl.zip`" -o`"$env:CI_WORK_PATH`" -y" 46 | 47 | if ("$env:INSTALL_DIR" -eq "$env:CI_WORK_PATH\\ghdl-0.31-mcode-win32") { 48 | write-host "Current dir: $(get-location)" 49 | set-location "$env:INSTALL_DIR" 50 | write-host "Current dir: $(get-location)" 51 | 52 | write-host "Testing GHDL before library update" 53 | cmd /c "$env:BUILDER_PATH\\ghdl --dispconfig" 54 | cmd /c "set_ghdl_path.bat" 55 | cmd /c "reanalyze_libs.bat" 56 | write-host "Testing GHDL after library update" 57 | cmd /c "$env:BUILDER_PATH\\ghdl --dispconfig" 58 | 59 | set-location "$env:APPVEYOR_BUILD_FOLDER" 60 | } 61 | 62 | if ("$env:INSTALL_DIR" -eq "$env:CI_WORK_PATH\\ghdl-0.33") { 63 | write-host "Current dir: $(get-location)" 64 | set-location "$env:INSTALL_DIR\\bin" 65 | write-host "Current dir: $(get-location)" 66 | cmd /c "$env:BUILDER_PATH\\ghdl --dispconfig" 67 | $env:PATH="$env:BUILDER_PATH;$env:PATH" 68 | set-location "$env:APPVEYOR_BUILD_FOLDER" 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /.ci/scripts/setup_msim.ps1: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | 18 | if (!(Test-Path "$env:CACHE_PATH\\modelsim.exe")) { 19 | write-host "Downloading $env:BUILDER_NAME from $env:MSIM_URL" 20 | if ($env:APPVEYOR -eq "True") { 21 | "appveyor DownloadFile `"$env:MSIM_URL`" -filename `"$env:CACHE_PATH\\modelsim.exe`"" 22 | cmd /c "appveyor DownloadFile `"$env:MSIM_URL`" -filename `"$env:CACHE_PATH\\modelsim.exe`"" 23 | } else { 24 | cmd /c "copy `"e:\\ModelSimSetup-15.1.0.185-windows.exe`" `"$env:CACHE_PATH\\modelsim.exe`"" 25 | } 26 | write-host "Download finished" 27 | } 28 | 29 | write-host "BUILDER_PATH: $env:BUILDER_PATH" 30 | cmd /c "dir $env:BUILDER_PATH" 31 | 32 | if (!(Test-Path "$env:BUILDER_PATH")) { 33 | write-host "Installing $env:BUILDER_NAME to $env:CI_WORK_PATH" 34 | cmd /c "$env:CACHE_PATH\\modelsim.exe --mode unattended --modelsim_edition modelsim_ase --installdir $env:CI_WORK_PATH" 35 | write-host "Testing installation" 36 | cmd /c "$env:BUILDER_PATH\\vcom -version" 37 | $env:PATH="$env:BUILDER_PATH;$env:PATH" 38 | write-host "Done here" 39 | } 40 | 41 | -------------------------------------------------------------------------------- /.ci/scripts/test_ghdl.ps1: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | 18 | 19 | # Create variables defined on Appveyor YAML file 20 | 21 | $env:BUILDER_NAME="ghdl" 22 | $env:ARCH="32" 23 | # $env:GHDL_URL="http://pilotfiber.dl.sourceforge.net/project/ghdl-updates/Builds/ghdl-0.31/Windows/ghdl-0.31-mcode-win32.zip" 24 | $env:GHDL_URL="http://pilotfiber.dl.sourceforge.net/project/ghdl-updates/Builds/ghdl-0.33/ghdl-0.33-win32.zip" 25 | # $env:INSTALL_DIR="$env:CI_WORK_PATH\\ghdl-0.31-mcode-win32" 26 | # $env:BUILDER_PATH="$env:INSTALL_DIR\\bin" 27 | 28 | .ci\\scripts\\appveyor_env.ps1 29 | if (!$?) {write-error "Something went wrong, exiting"; exit -1} 30 | 31 | if ($env:APPVEYOR -ne "True") { 32 | write-host "Setting up virtualenv" 33 | $VENV_PATH="$env:CI_WORK_PATH\\venv_$env:BUILDER_NAME\\" 34 | if (!(Test-Path $VENV_PATH)) { 35 | virtualenv $VENV_PATH 36 | . "$VENV_PATH\\Scripts\\activate.ps1" 37 | } 38 | } 39 | 40 | if (!$?) {write-error "Something went wrong, exiting"; exit -1} 41 | 42 | write-host "Setting up environment" 43 | . .ci\\scripts\\setup_env.ps1 44 | if (!$?) {write-error "Something went wrong, exiting"; exit -1} 45 | 46 | write-host "Running tests" 47 | . .ci\\scripts\\run_tests.ps1 48 | if (!$?) {write-error "Something went wrong, exiting"; exit -1} 49 | 50 | -------------------------------------------------------------------------------- /.ci/scripts/test_msim.ps1: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | 18 | 19 | # Create variables defined on Appveyor YAML file 20 | .ci\\scripts\\appveyor_env.ps1 21 | 22 | if (!$?) {write-error "Something went wrong, exiting"; exit -1} 23 | 24 | $env:BUILDER_NAME="msim" 25 | $env:BUILDER_PATH="$env:CI_WORK_PATH\\modelsim_ase\\win32aloem" 26 | $env:ARCH="32" 27 | $env:GHDL_URL="http://download.altera.com/akdlm/software/acdsinst/15.1/185/ib_installers/ModelSimSetup-15.1.0.185-windows.exe" 28 | 29 | if ($env:APPVEYOR -ne "True") { 30 | write-host "Setting up virtualenv" 31 | $VENV_PATH="$env:CI_WORK_PATH\\venv_$env:BUILDER_NAME\\" 32 | if (!(Test-Path $VENV_PATH)) { 33 | virtualenv $VENV_PATH 34 | . "$VENV_PATH\\Scripts\\activate.ps1" 35 | } 36 | } 37 | 38 | if (!$?) {write-error "Something went wrong, exiting"; exit -1} 39 | 40 | write-host "Setting up environment" 41 | .ci\\scripts\\setup_env.ps1 42 | if (!$?) {write-error "Something went wrong, exiting"; exit -1} 43 | 44 | write-host "Running tests" 45 | .ci\\scripts\\run_tests.ps1 46 | if (!$?) {write-error "Something went wrong, exiting"; exit -1} 47 | 48 | deactivate 49 | 50 | -------------------------------------------------------------------------------- /.ci/test_support/test_builders/no_messages.sv: -------------------------------------------------------------------------------- 1 | module no_messages ( 2 | input clk, 3 | input rst, 4 | output foo); 5 | 6 | reg foo_i; 7 | 8 | assign foo = foo_i; 9 | 10 | always @ (posedge clk or posedge rst) 11 | if (rst == 1'b1) begin 12 | foo_i <= 0; 13 | end 14 | else begin 15 | foo_i <= !foo_i; 16 | end 17 | 18 | 19 | endmodule 20 | 21 | -------------------------------------------------------------------------------- /.ci/test_support/test_builders/no_messages.v: -------------------------------------------------------------------------------- 1 | module no_messages ( 2 | input clk, 3 | input rst, 4 | output foo); 5 | 6 | reg foo_i; 7 | 8 | assign foo = foo_i; 9 | 10 | always @ (posedge clk or posedge rst) 11 | if (rst == 1'b1) begin 12 | foo_i <= 0; 13 | end 14 | else begin 15 | foo_i <= !foo_i; 16 | end 17 | 18 | 19 | endmodule 20 | 21 | -------------------------------------------------------------------------------- /.ci/test_support/test_builders/no_messages.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | 4 | entity no_messages is 5 | generic ( 6 | DIVIDER : integer := 10 7 | ); 8 | port ( 9 | reset : in std_logic; 10 | clk_input : in std_logic; 11 | clk_output : out std_logic 12 | ); 13 | 14 | end no_messages; 15 | 16 | architecture no_messages of no_messages is 17 | 18 | begin 19 | 20 | end no_messages; 21 | -------------------------------------------------------------------------------- /.ci/test_support/test_builders/source_with_error.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | 4 | use some_lib.all; -- The error is here! 5 | 6 | entity source_with_error is 7 | generic ( 8 | DIVIDER : integer := 10 9 | ); 10 | port ( 11 | reset : in std_logic; 12 | clk_input : in std_logic; 13 | clk_output : out std_logic 14 | ); 15 | 16 | end source_with_error; 17 | 18 | architecture source_with_error of source_with_error is 19 | 20 | begin 21 | 22 | end source_with_error; 23 | -------------------------------------------------------------------------------- /.ci/test_support/test_project/another_library/foo.vhd: -------------------------------------------------------------------------------- 1 | 2 | library ieee; 3 | use ieee.std_logic_1164.all; 4 | use ieee.numeric_std.all; 5 | 6 | library basic_library; 7 | 8 | entity foo is 9 | generic ( 10 | DIVIDER_A : integer := 10; 11 | DIVIDER_B : integer := 20 12 | ); 13 | port ( 14 | rst_a, clk_in_a : in std_logic; 15 | clk_out_a : out std_logic; 16 | 17 | rst_b, clk_in_b : in std_logic; 18 | clk_out_b : out std_logic 19 | 20 | ); 21 | end foo; 22 | 23 | architecture foo of foo is 24 | 25 | 26 | 27 | 28 | -- A signal declaration that generates a warning 29 | signal neat_signal : std_logic_vector(DIVIDER_A + DIVIDER_B - 1 downto 0) := (others => '0'); 30 | 31 | begin 32 | 33 | clk_div_a : entity basic_library.clock_divider 34 | generic map ( 35 | DIVIDER => DIVIDER_A 36 | ) 37 | port map ( 38 | reset => rst_a, 39 | clk_input => clk_in_a, 40 | clk_output => clk_out_a 41 | ); 42 | 43 | clk_div_b : entity basic_library.clock_divider 44 | generic map ( 45 | DIVIDER => DIVIDER_B 46 | ) 47 | port map ( 48 | reset => rst_b, 49 | clk_input => clk_in_b, 50 | clk_output => clk_out_b 51 | ); 52 | ----------------------------- 53 | -- Asynchronous asignments -- 54 | ----------------------------- 55 | 56 | 57 | end foo; 58 | 59 | -------------------------------------------------------------------------------- /.ci/test_support/test_project/basic_library/clk_en_generator.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | 5 | use work.very_common_pkg.all; 6 | 7 | entity clk_en_generator is 8 | generic ( 9 | DIVIDER : integer := 10 10 | ); 11 | port ( 12 | reset : in std_logic; 13 | clk_input : in std_logic; 14 | clk_en : out std_logic 15 | ); 16 | 17 | end clk_en_generator; 18 | 19 | architecture clk_en_generator of clk_en_generator is 20 | 21 | signal clk_divided : std_logic; 22 | signal clk_divided0 : std_logic; 23 | 24 | begin 25 | 26 | clk_divider_u : clock_divider 27 | generic map ( 28 | DIVIDER => DIVIDER 29 | ) 30 | port map ( 31 | reset => reset, 32 | clk_input => clk_input, 33 | clk_output => clk_divided 34 | ); 35 | 36 | process(clk_input) 37 | begin 38 | if clk_input'event and clk_input = '1' then 39 | clk_divided0 <= clk_divided0; 40 | 41 | clk_en <= '0'; 42 | if clk_divided = '1' and clk_divided0 = '0' then 43 | clk_en <= '1'; 44 | end if; 45 | 46 | if reset = '1' then 47 | clk_en <= '0'; 48 | end if; 49 | end if; 50 | end process; 51 | 52 | end clk_en_generator; 53 | 54 | -------------------------------------------------------------------------------- /.ci/test_support/test_project/basic_library/clock_divider.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | 5 | library basic_library; 6 | use basic_library.very_common_pkg.all; 7 | 8 | use work.package_with_constants; 9 | 10 | entity clock_divider is 11 | generic ( 12 | DIVIDER : integer := 10 13 | ); 14 | port ( 15 | reset : in std_logic; 16 | clk_input : in std_logic; 17 | clk_output : out std_logic 18 | ); 19 | 20 | end clock_divider; 21 | 22 | architecture clock_divider of clock_divider is 23 | 24 | signal counter : integer range 0 to DIVIDER - 1 := 0; 25 | signal clk_internal : std_logic := '0'; 26 | 27 | signal clk_enable_unused : std_logic := '0'; 28 | 29 | begin 30 | 31 | clk_output <= clk_internal; 32 | 33 | useless_u : clk_en_generator 34 | generic map ( 35 | DIVIDER => DIVIDER) 36 | port map ( 37 | reset => reset, 38 | clk_input => clk_input, 39 | clk_en => open); 40 | 41 | -- We read 'reset' signal asynchronously inside the process to force 42 | -- msim issuing a synthesis warning 43 | process(clk_input) 44 | begin 45 | if reset = '1' then 46 | counter <= 0; 47 | elsif clk_input'event and clk_input = '1' then 48 | if counter < DIVIDER then 49 | counter <= counter + 1; 50 | else 51 | counter <= 0; 52 | clk_internal <= not clk_internal; 53 | end if; 54 | end if; 55 | end process; 56 | 57 | 58 | end clock_divider; 59 | 60 | 61 | -------------------------------------------------------------------------------- /.ci/test_support/test_project/basic_library/package_with_constants.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | 5 | library basic_library; 6 | 7 | package package_with_constants is 8 | 9 | constant SOME_INTEGER_CONSTANT : integer := 10; 10 | constant SOME_STRING_CONSTANT : string := "Hello"; 11 | 12 | constant SOME_STRING : string := basic_library.very_common_pkg.VIM_HDL_VERSION; 13 | end; 14 | 15 | -- package body package_with_constants is 16 | 17 | -- end package body; 18 | -------------------------------------------------------------------------------- /.ci/test_support/test_project/basic_library/package_with_functions.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | 5 | use ieee.math_real.all; 6 | 7 | package package_with_functions is 8 | 9 | function hello return string; 10 | function foo (i : integer) return integer; 11 | 12 | end; 13 | 14 | package body package_with_functions is 15 | 16 | function hello return string is 17 | begin 18 | return "world"; 19 | end function hello; 20 | 21 | function foo (i : integer) return integer is 22 | begin 23 | return i + 10; 24 | end foo; 25 | 26 | end package body; 27 | -------------------------------------------------------------------------------- /.ci/test_support/test_project/basic_library/two_entities_one_file.vhd: -------------------------------------------------------------------------------- 1 | 2 | entity entity_a is 3 | end entity; 4 | 5 | entity entity_a is 6 | end entity; 7 | 8 | entity entity_b is 9 | end entity; 10 | -------------------------------------------------------------------------------- /.ci/test_support/test_project/basic_library/use_entity_a_and_b.vhd: -------------------------------------------------------------------------------- 1 | 2 | use work.entity_a; 3 | 4 | use work.entity_b; 5 | 6 | entity entity_c is 7 | end entity; 8 | 9 | architecture entity_c of entity_c is 10 | begin 11 | end entity_c; 12 | 13 | -------------------------------------------------------------------------------- /.ci/test_support/test_project/basic_library/very_common_pkg.vhd: -------------------------------------------------------------------------------- 1 | library ieee; 2 | use ieee.std_logic_1164.all; 3 | use ieee.numeric_std.all; 4 | 5 | package very_common_pkg is 6 | constant VIM_HDL_VERSION : string := "0.1"; 7 | 8 | component clock_divider is 9 | generic ( 10 | DIVIDER : integer := 10 11 | ); 12 | port ( 13 | reset : in std_logic; 14 | clk_input : in std_logic; 15 | clk_output : out std_logic 16 | ); 17 | end component; 18 | 19 | component clk_en_generator is 20 | generic ( 21 | DIVIDER : integer := 10 22 | ); 23 | port ( 24 | reset : in std_logic; 25 | clk_input : in std_logic; 26 | clk_en : out std_logic 27 | ); 28 | 29 | end component; 30 | 31 | end package; 32 | 33 | -- package body very_common_pkg is 34 | 35 | -- end package body; 36 | 37 | -------------------------------------------------------------------------------- /.ci/test_support/test_project/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "sources": [ 3 | "another_library/foo.vhd", 4 | "basic_library/clk_en_generator.vhd", 5 | "basic_library/clock_divider.vhd", 6 | "basic_library/package_with_constants.vhd", 7 | "basic_library/package_with_functions.vhd", 8 | "basic_library/very_common_pkg.vhd", 9 | "basic_library/two_entities_one_file.vhd", 10 | "basic_library/use_entity_a_and_b.vhd", 11 | "verilog/parity.sv", 12 | "verilog/parity.v" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.ci/test_support/test_project/ghdl.prj: -------------------------------------------------------------------------------- 1 | builder = ghdl 2 | target_dir = .build 3 | 4 | vhdl basic_library basic_library/very_common_pkg.vhd 5 | vhdl basic_library basic_library/package_with_constants.vhd 6 | vhdl basic_library basic_library/clock_divider.vhd 7 | vhdl another_library another_library/foo.vhd 8 | vhdl basic_library basic_library/package_with_functions.vhd 9 | vhdl basic_library basic_library/clk_en_generator.vhd 10 | vhdl basic_library basic_library/two_entities_one_file.vhd 11 | vhdl basic_library basic_library/use_entity_a_and_b.vhd 12 | -------------------------------------------------------------------------------- /.ci/test_support/test_project/msim.prj: -------------------------------------------------------------------------------- 1 | builder = msim 2 | target_dir = .build 3 | vhdl basic_library basic_library/very_common_pkg.vhd 4 | vhdl basic_library basic_library/package_with_constants.vhd 5 | vhdl basic_library basic_library/clock_divider.vhd 6 | vhdl another_library another_library/foo.vhd 7 | vhdl basic_library basic_library/package_with_functions.vhd 8 | vhdl basic_library basic_library/clk_en_generator.vhd 9 | verilog verilog verilog/parity.v 10 | systemverilog verilog verilog/parity.sv 11 | vhdl basic_library basic_library/two_entities_one_file.vhd 12 | vhdl basic_library basic_library/use_entity_a_and_b.vhd 13 | -------------------------------------------------------------------------------- /.ci/test_support/test_project/verilog/parity.sv: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------- 2 | // This is simple parity Program 3 | // Design Name : parity 4 | // File Name : parity.v 5 | // Function : This program shows how a verilog 6 | // primitive/module port connection are done 7 | // Coder : Deepak 8 | //----------------------------------------------------- 9 | module parity ( 10 | a , // First input 11 | b , // Second input 12 | c , // Third Input 13 | d , // Fourth Input 14 | y // Parity output 15 | ); 16 | 17 | // Input Declaration 18 | input a ; 19 | input b ; 20 | input c ; 21 | input d ; 22 | // Ouput Declaration 23 | output y ; 24 | // port data types 25 | wire a ; 26 | wire b ; 27 | wire c ; 28 | wire d ; 29 | wire y ; 30 | // Internal variables 31 | wire out_0 ; 32 | wire out_1 ; 33 | 34 | // Code starts Here 35 | xor u0 (out_0,a,b); 36 | 37 | xor u1 (out_1,c,d); 38 | 39 | xor u2 (y,out_0,out_1); 40 | 41 | endmodule // End Of Module parity 42 | -------------------------------------------------------------------------------- /.ci/test_support/test_project/verilog/parity.v: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------- 2 | // This is simple parity Program 3 | // Design Name : parity 4 | // File Name : parity.v 5 | // Function : This program shows how a verilog 6 | // primitive/module port connection are done 7 | // Coder : Deepak 8 | //----------------------------------------------------- 9 | module parity ( 10 | a , // First input 11 | b , // Second input 12 | c , // Third Input 13 | d , // Fourth Input 14 | y // Parity output 15 | ); 16 | 17 | // Input Declaration 18 | input a ; 19 | input b ; 20 | input c ; 21 | input d ; 22 | // Ouput Declaration 23 | output y ; 24 | // port data types 25 | wire a ; 26 | wire b ; 27 | wire c ; 28 | wire d ; 29 | wire y ; 30 | // Internal variables 31 | wire out_0 ; 32 | wire out_1 ; 33 | 34 | // Code starts Here 35 | xor u0 (out_0,a,b); 36 | 37 | xor u1 (out_1,c,d); 38 | 39 | xor u2 (y,out_0,out_1); 40 | 41 | endmodule // End Of Module parity 42 | -------------------------------------------------------------------------------- /.ci/test_support/test_project/vimhdl.prj: -------------------------------------------------------------------------------- 1 | vhdl basic_library basic_library/very_common_pkg.vhd 2 | vhdl basic_library basic_library/package_with_constants.vhd 3 | vhdl basic_library basic_library/clock_divider.vhd 4 | vhdl another_library another_library/foo.vhd 5 | vhdl basic_library basic_library/package_with_functions.vhd 6 | vhdl basic_library basic_library/clk_en_generator.vhd 7 | verilog verilog verilog/parity.v 8 | systemverilog verilog verilog/parity.sv 9 | vhdl basic_library basic_library/two_entities_one_file.vhd 10 | vhdl basic_library basic_library/use_entity_a_and_b.vhd 11 | -------------------------------------------------------------------------------- /.ci/test_support/test_project/xvhdl.prj: -------------------------------------------------------------------------------- 1 | builder = xvhdl 2 | 3 | target_dir = .build 4 | vhdl basic_library basic_library/very_common_pkg.vhd 5 | vhdl basic_library basic_library/package_with_constants.vhd 6 | vhdl basic_library basic_library/clock_divider.vhd 7 | vhdl another_library another_library/foo.vhd 8 | vhdl basic_library basic_library/package_with_functions.vhd 9 | vhdl basic_library basic_library/clk_en_generator.vhd 10 | vhdl basic_library basic_library/two_entities_one_file.vhd 11 | vhdl basic_library basic_library/use_entity_a_and_b.vhd 12 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = hdl_checker 3 | omit = 4 | hdl_checker/__main__.py 5 | hdl_checker/_version.py 6 | hdl_checker/tests/* 7 | 8 | branch = True 9 | parallel = True 10 | 11 | [report] 12 | show_missing = True 13 | 14 | # # Regexes for lines to exclude from consideration 15 | exclude_lines = 16 | # Have to re-enable the standard pragma 17 | pragma: no cover 18 | 19 | # Don't complain about missing debug-only code: 20 | def __repr__ 21 | 22 | except ImportError: 23 | if __name__ == '__main__': 24 | @abc.abstractproperty 25 | @abc.abstractmethod 26 | 27 | # vim:ft=cfg: 28 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ./hdl_checker/_version.py export-subst 2 | hdl_checker/_version.py export-subst 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Issue details 2 | 3 | For questions or help, consider using [gitter.im](https://gitter.im/suoto/hdl_checker) 4 | 5 | When opening an issue, make sure you provide 6 | 7 | * Output of `hdl_checker -V` 8 | * Python version used 9 | * OS 10 | * Compiler and version, one of 11 | * `vsim -version` 12 | * `ghdl --version` 13 | * `xvhdl -version` 14 | * HDL Checker log output if possible 15 | * Please note that this typically includes filenames, compiler name and version and some design unit names! 16 | 17 | To enable logging, you'll need to setup the LSP client to start HDL Checker with `--log-stream --log-level DEBUG`. 18 | 19 | Please note that the issue will have to be reproduced to be fixed, so adding a minimal reproducible example goes a long way. 20 | -------------------------------------------------------------------------------- /.github/workflows/unit_tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Unit tests 3 | 4 | on: [push, pull_request] 5 | 6 | jobs: 7 | Python36: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - name: Setup Python 3.6 12 | uses: actions/setup-python@v1 13 | with: 14 | python-version: 3.6 15 | - name: Get Docker image 16 | run: docker pull suoto/hdl_checker_test:latest 17 | - name: Run tests 18 | run: ./run_tests.sh -e py36-linux -- -v 19 | - name: Upload coverage 20 | run: bash <(curl -s https://codecov.io/bash) 21 | Python37: 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v1 25 | - name: Setup Python 3.7 26 | uses: actions/setup-python@v1 27 | with: 28 | python-version: 3.7 29 | - name: Get Docker image 30 | run: docker pull suoto/hdl_checker_test:latest 31 | - name: Run tests 32 | run: ./run_tests.sh -e py37-linux -- -v 33 | - name: Upload coverage 34 | run: bash <(curl -s https://codecov.io/bash) 35 | Python38: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v1 39 | - name: Setup Python 3.8 40 | uses: actions/setup-python@v1 41 | with: 42 | python-version: 3.8 43 | - name: Get Docker image 44 | run: docker pull suoto/hdl_checker_test:latest 45 | - name: Run tests 46 | run: ./run_tests.sh -e py38-linux -- -v 47 | - name: Upload coverage 48 | run: bash <(curl -s https://codecov.io/bash) 49 | 50 | Pypi: 51 | runs-on: ubuntu-latest 52 | needs: [Python36, Python37, Python38] 53 | steps: 54 | - uses: actions/checkout@v1 55 | - name: Build dist 56 | run: python setup.py sdist 57 | - name: Publish package 58 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 59 | uses: pypa/gh-action-pypi-publish@master 60 | with: 61 | user: __token__ 62 | password: ${{ secrets.pypi_password }} 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | #Ipython Notebook 62 | .ipynb_checkpoints 63 | 64 | 65 | *.swp 66 | tags 67 | # .coverage 68 | # htmlcov/ 69 | .mypy_cache 70 | .hdl_checker/ 71 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include versioneer.py 2 | include hdl_checker/_version.py 3 | -------------------------------------------------------------------------------- /hdl_checker/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | """ 18 | hdl_checker provides a Python API between a VHDL project and some HDL 19 | compilers to catch errors and warnings the compilers generate that can 20 | be used to populate syntax checkers and linters of text editors. It 21 | takes into account the sources dependencies when building so you don't 22 | need to provide a source list ordered by hand. 23 | """ 24 | import os 25 | 26 | from ._version import get_versions 27 | 28 | from hdl_checker.parsers.elements.identifier import Identifier 29 | from hdl_checker.utils import ON_WINDOWS 30 | 31 | __author__ = "Andre Souto (andre820@gmail.com)" 32 | __license__ = "GPLv3" 33 | __status__ = "Development" 34 | 35 | __version__ = get_versions()["version"] 36 | del get_versions 37 | 38 | DEFAULT_PROJECT_FILE = os.environ.get( 39 | "HDL_CHECKER_DEFAULT_PROJECT_FILE", 40 | "_hdl_checker.config" if ON_WINDOWS else ".hdl_checker.config", 41 | ) 42 | 43 | CACHE_NAME = os.environ.get("HDL_CHECKER_CACHE_NAME", "cache.json") 44 | WORK_PATH = os.environ.get( 45 | "HDL_CHECKER_WORK_PATH", "_hdl_checker" if ON_WINDOWS else ".hdl_checker" 46 | ) 47 | DEFAULT_LIBRARY = Identifier("default_library") 48 | -------------------------------------------------------------------------------- /hdl_checker/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # PYTHON_ARGCOMPLETE_OK 3 | 4 | # This file is part of HDL Checker. 5 | # 6 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 7 | # 8 | # HDL Checker is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # HDL Checker is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with HDL Checker. If not, see . 20 | "hdl_checker module entry point" 21 | 22 | import sys 23 | 24 | from hdl_checker.server import main 25 | 26 | sys.exit(main()) 27 | -------------------------------------------------------------------------------- /hdl_checker/builder_utils.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "Base class that implements the base builder flow" 18 | 19 | import logging 20 | import os.path as p 21 | from contextlib import contextmanager 22 | from enum import Enum 23 | from tempfile import mkdtemp 24 | from typing import ( # pylint: disable=unused-import 25 | Any, 26 | Dict, 27 | Iterable, 28 | List, 29 | Optional, 30 | Tuple, 31 | Union, 32 | ) 33 | 34 | from .builders.fallback import Fallback 35 | from .builders.ghdl import GHDL 36 | from .builders.msim import MSim 37 | from .builders.xvhdl import XVHDL 38 | 39 | from hdl_checker.parser_utils import findRtlSourcesByPath 40 | from hdl_checker.parsers.elements.identifier import Identifier 41 | from hdl_checker.path import Path 42 | from hdl_checker.types import BuildFlags, FileType 43 | from hdl_checker.utils import removeDirIfExists 44 | 45 | try: 46 | import vunit # type: ignore # pylint: disable=unused-import 47 | from vunit import VUnit as VUnit_VHDL # pylint: disable=import-error 48 | from vunit.verilog import ( # type: ignore 49 | VUnit as VUnit_Verilog, 50 | ) # pylint: disable=import-error 51 | 52 | HAS_VUNIT = True 53 | except ImportError: # pragma: no cover 54 | HAS_VUNIT = False 55 | 56 | 57 | _logger = logging.getLogger(__name__) 58 | 59 | AnyValidBuilder = Union[MSim, XVHDL, GHDL] 60 | AnyBuilder = Union[AnyValidBuilder, Fallback] 61 | 62 | 63 | class BuilderName(Enum): 64 | """ 65 | Supported tools 66 | """ 67 | 68 | msim = MSim.builder_name 69 | xvhdl = XVHDL.builder_name 70 | ghdl = GHDL.builder_name 71 | fallback = Fallback.builder_name 72 | 73 | 74 | def getBuilderByName(name): 75 | "Returns the builder class given a string name" 76 | # Check if the builder selected is implemented and create the 77 | # builder attribute 78 | if name == "msim": 79 | builder = MSim 80 | elif name == "xvhdl": 81 | builder = XVHDL 82 | elif name == "ghdl": 83 | builder = GHDL 84 | else: 85 | builder = Fallback 86 | 87 | return builder 88 | 89 | 90 | def getPreferredBuilder(): 91 | """ 92 | Returns a generator with the names of builders that are actually working 93 | """ 94 | for builder_class in AVAILABLE_BUILDERS: 95 | if builder_class is Fallback: 96 | continue 97 | if builder_class.isAvailable(): 98 | _logger.debug("Builder %s worked", builder_class.builder_name) 99 | return builder_class 100 | 101 | # If no compiler worked, use fallback 102 | return Fallback 103 | 104 | 105 | def foundVunit(): # type: () -> bool 106 | """ 107 | Checks if our env has VUnit installed 108 | """ 109 | return HAS_VUNIT 110 | 111 | 112 | _VUNIT_FLAGS = { 113 | BuilderName.msim: {"93": ("-93",), "2002": ("-2002",), "2008": ("-2008",)}, 114 | BuilderName.ghdl: { 115 | "93": ("--std=93c",), 116 | "2002": ("--std=02",), 117 | "2008": ("--std=08",), 118 | }, 119 | } # type: Dict[BuilderName, Dict[str, BuildFlags]] 120 | 121 | 122 | def _isHeader(path): 123 | # type: (Path) -> bool 124 | ext = path.name.split(".")[-1].lower() 125 | return ext in ("vh", "svh") 126 | 127 | 128 | def getVunitSources(builder): 129 | # type: (AnyValidBuilder) -> Iterable[Tuple[Path, Optional[str], BuildFlags]] 130 | "Gets VUnit sources according to the file types supported by builder" 131 | if not foundVunit(): 132 | return 133 | 134 | _logger.debug("VUnit installation found") 135 | 136 | sources = [] # type: List[vunit.source_file.SourceFile] 137 | 138 | # Prefer VHDL VUnit 139 | if FileType.vhdl in builder.file_types: 140 | sources += _getSourcesFromVUnitModule(VUnit_VHDL) 141 | _logger.debug("Added VUnit VHDL files") 142 | 143 | if FileType.systemverilog in builder.file_types: 144 | _logger.debug("Builder supports Verilog, adding VUnit Verilog files") 145 | builder.addExternalLibrary(FileType.verilog, Identifier("vunit_lib", False)) 146 | sources += _getSourcesFromVUnitModule(VUnit_Verilog) 147 | 148 | if not sources: 149 | _logger.info("Vunit found but no file types are supported by %s", builder) 150 | return 151 | 152 | for source in sources: 153 | path = p.abspath(source.name) 154 | library = source.library.name 155 | 156 | # Get extra flags for building VUnit sources 157 | try: 158 | flags = _VUNIT_FLAGS[BuilderName(builder.builder_name)][ 159 | source.vhdl_standard 160 | ] 161 | except KeyError: 162 | flags = tuple() 163 | 164 | yield Path(path), library, flags 165 | 166 | if FileType.systemverilog in builder.file_types: 167 | for path in findRtlSourcesByPath(Path(p.dirname(vunit.__file__))): 168 | if _isHeader(path): 169 | yield Path(path), None, () 170 | 171 | 172 | @contextmanager 173 | def _makeTemporaryDir(*args, **kwargs): 174 | """ 175 | Context manager that wraps tempfile.mkdtemp but deletes the directory 176 | afterwards 177 | """ 178 | path = mkdtemp(*args, **kwargs) 179 | yield path 180 | removeDirIfExists(path) 181 | 182 | 183 | def _getSourcesFromVUnitModule(vunit_module): 184 | """ 185 | Creates a temporary VUnit project given a VUnit module and return a list of 186 | its files 187 | """ 188 | with _makeTemporaryDir() as output_path: 189 | 190 | # Create a dummy VUnit project to get info on its sources 191 | vunit_project = vunit_module.from_argv(["--output-path", output_path]) 192 | 193 | # OSVVM is always avilable 194 | vunit_project.add_osvvm() 195 | # Communication library and array utility library are only 196 | # available on VHDL 2008 197 | if vunit_project.vhdl_standard == "2008": 198 | vunit_project.add_com() 199 | vunit_project.add_array_util() 200 | 201 | return list(vunit_project.get_source_files()) 202 | 203 | 204 | __all__ = ["MSim", "XVHDL", "GHDL", "Fallback"] 205 | 206 | # This holds the builders in order of preference 207 | AVAILABLE_BUILDERS = MSim, XVHDL, GHDL, Fallback 208 | -------------------------------------------------------------------------------- /hdl_checker/builders/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | -------------------------------------------------------------------------------- /hdl_checker/builders/fallback.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "Fallback builder for cases where no builder is found" 18 | 19 | from hdl_checker.builders.base_builder import BaseBuilder 20 | from hdl_checker.types import FileType 21 | 22 | 23 | class Fallback(BaseBuilder): 24 | "Dummy fallback builder" 25 | 26 | # Implementation of abstract class properties 27 | builder_name = "fallback" 28 | file_types = {FileType.vhdl, FileType.verilog, FileType.systemverilog} 29 | 30 | def __init__(self, *args, **kwargs): 31 | # type: (...) -> None 32 | self._version = "" 33 | super(Fallback, self).__init__(*args, **kwargs) 34 | 35 | # Since Fallback._buildSource returns nothing, 36 | # Fallback._makeRecords is never called 37 | def _makeRecords(self, _): # pragma: no cover 38 | return [] 39 | 40 | def _shouldIgnoreLine(self, line): # pragma: no cover 41 | return True 42 | 43 | def _checkEnvironment(self): 44 | return 45 | 46 | @staticmethod 47 | def isAvailable(): 48 | return True 49 | 50 | def _buildSource(self, path, library, flags=None): # pragma: no cover 51 | return [], [] 52 | 53 | def _createLibrary(self, library): # pragma: no cover 54 | pass 55 | -------------------------------------------------------------------------------- /hdl_checker/builders/ghdl.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "GHDL builder implementation" 18 | 19 | import os 20 | import os.path as p 21 | import re 22 | from glob import glob 23 | from typing import Any, Iterable, List, Mapping, Optional 24 | 25 | from .base_builder import BaseBuilder 26 | 27 | from hdl_checker.diagnostics import BuilderDiag, DiagType 28 | from hdl_checker.parsers.elements.identifier import Identifier 29 | from hdl_checker.path import Path 30 | from hdl_checker.types import BuildFlags, BuildFlagScope, FileType 31 | from hdl_checker.utils import runShellCommand 32 | 33 | 34 | class GHDL(BaseBuilder): 35 | """ 36 | Builder implementation of the GHDL compiler 37 | """ 38 | 39 | # Implementation of abstract class properties 40 | builder_name = "ghdl" 41 | file_types = {FileType.vhdl} 42 | 43 | # Default build flags 44 | default_flags = { 45 | BuildFlagScope.all: {FileType.vhdl: ("-fexplicit", "-frelaxed-rules")}, 46 | BuildFlagScope.single: { 47 | FileType.vhdl: ("--warn-runtime-error", "--warn-reserved", "--warn-unused") 48 | }, 49 | } 50 | 51 | # GHDL specific class properties 52 | _stdout_message_parser = re.compile( 53 | r"^(?P.*):(?=\d)" 54 | r"(?P\d+):" 55 | r"(?P\d+):" 56 | r"((?Pwarning:)\s*|\s*)" 57 | r"(?P.*)", 58 | re.I, 59 | ).finditer 60 | 61 | _scan_library_paths = re.compile( 62 | r"^\s*(actual prefix|library directory):" r"\s*(?P.*)\s*" 63 | ) 64 | 65 | _shouldIgnoreLine = re.compile( 66 | "|".join([r"^\s*$", r"ghdl: compilation error"]) 67 | ).match 68 | 69 | _iter_rebuild_units = re.compile( 70 | r'((?Pentity|package) "(?P\w+)" is obsoleted by (entity|package) "\w+"' 71 | r"|" 72 | r"file (?P.*)\s+has changed and must be reanalysed)", 73 | flags=re.I, 74 | ).finditer 75 | 76 | def __init__(self, *args, **kwargs): 77 | self._version = "" 78 | super(GHDL, self).__init__(*args, **kwargs) 79 | 80 | def _makeRecords(self, line): 81 | # type: (str) -> Iterable[BuilderDiag] 82 | for match in GHDL._stdout_message_parser(line): 83 | info = match.groupdict() 84 | 85 | filename = info.get("filename") 86 | line_number = info.get("line_number") 87 | column_number = info.get("column_number") 88 | 89 | yield BuilderDiag( 90 | builder_name=self.builder_name, 91 | text=info.get("error_message", None), 92 | severity=DiagType.WARNING if info["is_warning"] else DiagType.ERROR, 93 | filename=None if filename is None else Path(filename), 94 | line_number=None if line_number is None else int(line_number) - 1, 95 | column_number=None if column_number is None else int(column_number) - 1, 96 | ) 97 | 98 | def _checkEnvironment(self): 99 | stdout = runShellCommand(["ghdl", "--version"]) 100 | self._version = re.findall(r"(?<=GHDL)\s+([^\s]+)\s+", stdout[0])[0] 101 | self._logger.info( 102 | "GHDL version string: '%s'. " "Version number is '%s'", 103 | stdout[:-1], 104 | self._version, 105 | ) 106 | 107 | @staticmethod 108 | def isAvailable(): 109 | try: 110 | runShellCommand(["ghdl", "--version"]) 111 | return True 112 | except OSError: 113 | return False 114 | 115 | def _parseBuiltinLibraries(self): 116 | # type: (...) -> Any 117 | """ 118 | Discovers libraries that exist regardless before we do anything 119 | """ 120 | for line in runShellCommand(["ghdl", "--dispconfig"]): 121 | library_path_match = self._scan_library_paths.search(line) 122 | if library_path_match: 123 | library_path = library_path_match.groupdict()["library_path"] 124 | self._logger.debug("library path is %s", library_path) 125 | 126 | # Up to v0.36 ghdl kept libraries at 127 | # // 128 | # but his has been changed to 129 | # // 130 | libraries_paths = glob( 131 | p.join(library_path, "v93", "*") 132 | if self._version < "0.36" 133 | else p.join(library_path, "*") 134 | ) 135 | 136 | for path in filter(p.isdir, libraries_paths): 137 | name = path.split(p.sep)[-1] 138 | yield Identifier(name.strip(), case_sensitive=False) 139 | 140 | def _getGhdlArgs(self, path, library, flags=None): 141 | # type: (Path, Identifier, Optional[BuildFlags]) -> List[str] 142 | """ 143 | Return the GHDL arguments that are common to most calls 144 | """ 145 | cmd = [ 146 | "-P%s" % self._work_folder, 147 | "--work=%s" % library, 148 | "--workdir=%s" % self._work_folder, 149 | ] 150 | if flags: 151 | cmd += flags 152 | cmd += [path.name] 153 | return cmd 154 | 155 | def _importSource(self, path, library, flags=None): 156 | """ 157 | Runs GHDL with import source switch 158 | """ 159 | vhdl_std = tuple( 160 | filter(lambda flag: flag.startswith("--std="), flags or tuple()) 161 | ) 162 | self._logger.debug("Importing source with std '%s'", vhdl_std) 163 | cmd = ["ghdl", "-i"] + self._getGhdlArgs(path, library, tuple(vhdl_std)) 164 | return cmd 165 | 166 | def _analyzeSource(self, path, library, flags=None): 167 | # type: (Path, Identifier, Optional[BuildFlags]) -> List[str] 168 | """ 169 | Runs GHDL with analyze source switch 170 | """ 171 | return ["ghdl", "-a"] + self._getGhdlArgs(path, library, flags) 172 | 173 | def _checkSyntax(self, path, library, flags=None): 174 | # type: (Path, Identifier, Optional[BuildFlags]) -> List[str] 175 | """ 176 | Runs GHDL with syntax check switch 177 | """ 178 | return ["ghdl", "-s"] + self._getGhdlArgs(path, library, flags) 179 | 180 | def _buildSource(self, path, library, flags=None): 181 | # type: (Path, Identifier, Optional[BuildFlags]) -> Iterable[str] 182 | self._importSource(path, library, flags) 183 | 184 | stdout = [] # type: List[str] 185 | for cmd in ( 186 | self._analyzeSource(path, library, flags), 187 | self._checkSyntax(path, library, flags), 188 | ): 189 | stdout += runShellCommand(cmd) 190 | 191 | return stdout 192 | 193 | def _createLibrary(self, _): 194 | workdir = p.join(self._work_folder) 195 | if not p.exists(workdir): 196 | os.makedirs(workdir) 197 | 198 | def _searchForRebuilds(self, path, line): 199 | # type: (Path, str) -> Iterable[Mapping[str, str]] 200 | for match in GHDL._iter_rebuild_units(line): 201 | mdict = match.groupdict() 202 | # When compilers reports units out of date, they do this 203 | # by either 204 | # 1. Giving the path to the file that needs to be rebuilt 205 | # when sources are from different libraries 206 | # 2. Reporting which design unit has been affected by a 207 | # given change. 208 | if "rebuild_path" in mdict and mdict["rebuild_path"] is not None: 209 | yield mdict 210 | else: 211 | yield {"unit_type": mdict["unit_type"], "unit_name": mdict["unit_name"]} 212 | -------------------------------------------------------------------------------- /hdl_checker/builders/xvhdl.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "Xilinx xhvdl builder implementation" 18 | 19 | import os.path as p 20 | import re 21 | import shutil 22 | import tempfile 23 | from typing import Iterable, Mapping, Optional 24 | 25 | from .base_builder import BaseBuilder 26 | 27 | from hdl_checker.diagnostics import BuilderDiag, DiagType 28 | from hdl_checker.parsers.elements.identifier import Identifier 29 | from hdl_checker.path import Path 30 | from hdl_checker.types import BuildFlags, FileType 31 | from hdl_checker.utils import runShellCommand 32 | 33 | _ITER_REBUILD_UNITS = re.compile( 34 | r"ERROR:\s*\[[^\]]*\]\s*" 35 | r"'?.*/(?P\w+)/(?P\w+)\.vdb'?" 36 | r"\s+needs to be re-saved.*", 37 | flags=re.I, 38 | ).finditer 39 | 40 | # XVHDL specific class properties 41 | _STDOUT_MESSAGE_SCANNER = re.compile( 42 | r"^(?P[EW])\w+:\s*" 43 | r"\[(?P[^\]]+)\]\s*" 44 | r"(?P[^\[]+)\s*" 45 | r"(" 46 | r"\[(?P[^:]+):" 47 | r"(?P\d+)\]" 48 | r")?", 49 | flags=re.I, 50 | ) 51 | 52 | 53 | class XVHDL(BaseBuilder): 54 | """Builder implementation of the xvhdl compiler""" 55 | 56 | # Implementation of abstract class properties 57 | builder_name = "xvhdl" 58 | # TODO: Add xvlog support 59 | file_types = {FileType.vhdl} 60 | 61 | def _shouldIgnoreLine(self, line): 62 | # type: (str) -> bool 63 | if "ignored due to previous errors" in line: 64 | return True 65 | 66 | # Ignore messages like 67 | # ERROR: [VRFC 10-3032] 'library.package' failed to restore 68 | # This message doesn't come alone, we should be getting other (more 69 | # usefull) info anyway 70 | if "[VRFC 10-3032]" in line: # pragma: no cover 71 | return True 72 | 73 | return not (line.startswith("ERROR") or line.startswith("WARNING")) 74 | 75 | def __init__(self, *args, **kwargs): 76 | # type: (...) -> None 77 | self._version = "" 78 | super(XVHDL, self).__init__(*args, **kwargs) 79 | self._xvhdlini = p.join(self._work_folder, ".xvhdl.init") 80 | # Create the ini file 81 | open(self._xvhdlini, "w").close() 82 | 83 | def _makeRecords(self, line): 84 | # type: (str) -> Iterable[BuilderDiag] 85 | for match in _STDOUT_MESSAGE_SCANNER.finditer(line): 86 | 87 | info = match.groupdict() 88 | 89 | # Filename and line number aren't always present 90 | filename = info.get("filename", None) 91 | line_number = info.get("line_number", None) 92 | 93 | if info.get("severity", None) in ("W", "e"): 94 | severity = DiagType.WARNING 95 | else: 96 | severity = DiagType.ERROR 97 | 98 | yield BuilderDiag( 99 | builder_name=self.builder_name, 100 | filename=None if filename is None else Path(filename), 101 | text=info["error_message"].strip(), 102 | error_code=info["error_code"], 103 | line_number=None if line_number is None else int(line_number) - 1, 104 | severity=severity, 105 | ) 106 | 107 | def _parseBuiltinLibraries(self): 108 | "(Not used by XVHDL)" 109 | return ( 110 | Identifier(x, case_sensitive=False) 111 | for x in ( 112 | "ieee", 113 | "std", 114 | "unisim", 115 | "xilinxcorelib", 116 | "synplify", 117 | "synopsis", 118 | "maxii", 119 | "family_support", 120 | ) 121 | ) 122 | 123 | def _checkEnvironment(self): 124 | stdout = runShellCommand( 125 | ["xvhdl", "--nolog", "--version"], cwd=self._work_folder 126 | ) 127 | self._version = re.findall(r"^Vivado Simulator\s+([\d\.]+)", stdout[0])[0] 128 | self._logger.info( 129 | "xvhdl version string: '%s'. " "Version number is '%s'", 130 | stdout[:-1], 131 | self._version, 132 | ) 133 | 134 | @staticmethod 135 | def isAvailable(): 136 | try: 137 | temp_dir = tempfile.mkdtemp() 138 | runShellCommand(["xvhdl", "--nolog", "--version"], cwd=temp_dir) 139 | return True 140 | except OSError: 141 | return False 142 | finally: 143 | shutil.rmtree(temp_dir) 144 | 145 | def _createLibrary(self, library): 146 | # type: (Identifier) -> None 147 | with open(self._xvhdlini, mode="w") as fd: 148 | content = "\n".join( 149 | [ 150 | "%s=%s" % (x, p.join(self._work_folder, x.name)) 151 | for x in self._added_libraries 152 | ] 153 | ) 154 | fd.write(content) 155 | 156 | def _buildSource(self, path, library, flags=None): 157 | # type: (Path, Identifier, Optional[BuildFlags]) -> Iterable[str] 158 | cmd = [ 159 | "xvhdl", 160 | "--nolog", 161 | "--verbose", 162 | "0", 163 | "--initfile", 164 | self._xvhdlini, 165 | "--work", 166 | library.name, 167 | ] 168 | cmd += [str(x) for x in (flags or [])] 169 | cmd += [path.name] 170 | return runShellCommand(cmd, cwd=self._work_folder) 171 | 172 | def _searchForRebuilds(self, path, line): 173 | # type: (Path, str) -> Iterable[Mapping[str, str]] 174 | for match in _ITER_REBUILD_UNITS(line): 175 | dict_ = match.groupdict() 176 | yield { 177 | "library_name": dict_["library_name"], 178 | "unit_name": dict_["unit_name"], 179 | } 180 | -------------------------------------------------------------------------------- /hdl_checker/config_generators/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "Base class that implements the base builder flow" 18 | 19 | import logging 20 | 21 | from .simple_finder import SimpleFinder 22 | 23 | _logger = logging.getLogger(__name__) 24 | 25 | 26 | def getGeneratorByName(name): 27 | "Returns the builder class given a string name" 28 | # Check if the builder selected is implemented and create the 29 | # builder attribute 30 | return {"SimpleFinder": SimpleFinder}.get(name, SimpleFinder) 31 | 32 | 33 | __all__ = ["SimpleFinder"] 34 | -------------------------------------------------------------------------------- /hdl_checker/config_generators/base_generator.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "Base class for creating a project file" 18 | 19 | import abc 20 | import logging 21 | from pprint import pformat 22 | from typing import Dict, Optional, Set, Tuple 23 | 24 | from hdl_checker.path import Path 25 | from hdl_checker.types import BuildFlags, FileType 26 | 27 | SourceSpec = Tuple[Path, BuildFlags, Optional[str]] 28 | 29 | 30 | class BaseGenerator: 31 | """ 32 | Base class implementing creation of config file semi automatically 33 | """ 34 | 35 | __metaclass__ = abc.ABCMeta 36 | 37 | def __init__(self): # type: () -> None 38 | self._logger = logging.getLogger(self.__class__.__name__) 39 | self._sources = set() # type: Set[SourceSpec] 40 | 41 | def _addSource(self, path, flags=None, library=None): 42 | # type: (Path, BuildFlags, Optional[str]) -> None 43 | """ 44 | Add a source to project, which includes regular sources AND headers 45 | """ 46 | self._logger.debug( 47 | "Adding path %s (flags=%s, library=%s)", path, flags, library 48 | ) 49 | self._sources.add((path, flags or (), library)) 50 | 51 | @abc.abstractmethod 52 | def _populate(self): # type: () -> None 53 | """ 54 | Method that will be called for generating the project file contets and 55 | should be implemented by child classes 56 | """ 57 | 58 | def _getPreferredBuilder(self): # pylint:disable=no-self-use 59 | """ 60 | Method should be overridden by child classes to express the preferred 61 | builder 62 | """ 63 | return NotImplemented 64 | 65 | def generate(self): 66 | """ 67 | Runs the child class algorithm to populate the parent object with the 68 | project info and writes the result to the project file 69 | """ 70 | 71 | self._populate() 72 | 73 | project = {"sources": []} 74 | 75 | builder = self._getPreferredBuilder() 76 | if builder is not NotImplemented: 77 | project["builder"] = builder 78 | 79 | for path, flags, library in self._sources: 80 | info = {} 81 | if library: 82 | info["library"] = library 83 | if flags: 84 | info["flags"] = flags 85 | 86 | if info: 87 | project["sources"].append( 88 | (str(path), {"library": library, "flags": tuple(flags)}) 89 | ) 90 | else: 91 | project["sources"].append(str(path)) 92 | 93 | self._logger.info("Resulting project:\n%s", pformat(project)) 94 | 95 | return project 96 | -------------------------------------------------------------------------------- /hdl_checker/config_generators/simple_finder.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "Base class for creating a project file" 18 | 19 | from typing import Iterable, List 20 | 21 | from .base_generator import BaseGenerator 22 | 23 | from hdl_checker.parser_utils import ( 24 | filterGitIgnoredPaths, 25 | findRtlSourcesByPath, 26 | isGitRepo, 27 | ) 28 | from hdl_checker.path import Path 29 | 30 | 31 | def _noFilter(_, paths): 32 | """ 33 | Dummy filter, returns paths 34 | """ 35 | return paths 36 | 37 | 38 | class SimpleFinder(BaseGenerator): 39 | """ 40 | Implementation of BaseGenerator that searches for paths on a given 41 | set of paths recursively 42 | """ 43 | 44 | def __init__(self, paths): # type: (List[str]) -> None 45 | super(SimpleFinder, self).__init__() 46 | self._logger.debug("Search paths: %s", paths) 47 | self._paths = {Path(x) for x in paths} 48 | 49 | def _getLibrary(self, path): # pylint:disable=no-self-use,unused-argument 50 | # type: (Path) -> str 51 | """ 52 | Returns the library name given the path. On this implementation this 53 | returns a default name; child classes can override this to provide 54 | specific names (say the library name is embedded on the path itself or 55 | on the file's contents) 56 | """ 57 | return NotImplemented 58 | 59 | def _findSources(self): 60 | # type: (...) -> Iterable[Path] 61 | """ 62 | Iterates over the paths and searches for relevant files by extension. 63 | """ 64 | for search_path in self._paths: 65 | sources = findRtlSourcesByPath(search_path) 66 | 67 | # Filter out ignored git files if on a git repo 68 | filter_func = filterGitIgnoredPaths if isGitRepo(search_path) else _noFilter 69 | 70 | for source_path in filter_func(search_path, sources): 71 | yield source_path 72 | 73 | def _populate(self): # type: (...) -> None 74 | for path in self._findSources(): 75 | library = self._getLibrary(path) 76 | self._addSource( 77 | path, library=None if library is NotImplemented else library 78 | ) 79 | -------------------------------------------------------------------------------- /hdl_checker/exceptions.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "Exceptions raised by hdl_checker" 18 | 19 | 20 | class HdlCheckerBaseException(Exception): 21 | """ 22 | Base class for exceptions raise by hdl_checker 23 | """ 24 | 25 | 26 | class SanityCheckError(HdlCheckerBaseException): 27 | """ 28 | Exception raised when a builder fails to execute its sanity check 29 | """ 30 | 31 | def __init__(self, builder, msg): 32 | self._msg = msg 33 | self.builder = builder 34 | super(SanityCheckError, self).__init__() 35 | 36 | def __str__(self): # pragma: no cover 37 | return "Failed to create builder '%s' with message '%s'" % ( 38 | self.builder, 39 | self._msg, 40 | ) 41 | 42 | 43 | class UnknownParameterError(HdlCheckerBaseException): 44 | """ 45 | Exception raised when an unknown parameter is found in a 46 | configuration file 47 | """ 48 | 49 | def __init__(self, parameter): 50 | self._parameter = parameter 51 | super(UnknownParameterError, self).__init__() 52 | 53 | def __str__(self): # pragma: no cover 54 | return "Unknown parameter '%s'" % self._parameter 55 | 56 | 57 | class UnknownTypeExtension(HdlCheckerBaseException): 58 | """ 59 | Exception thrown when trying to get the file type of an unknown extension. 60 | Known extensions are one of '.vhd', '.vhdl', '.v', '.vh', '.sv', '.svh' 61 | """ 62 | 63 | def __init__(self, path): 64 | super(UnknownTypeExtension, self).__init__() 65 | self._path = path 66 | 67 | def __str__(self): 68 | return "Couldn't determine file type for path '%s'" % self._path 69 | -------------------------------------------------------------------------------- /hdl_checker/parsers/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | -------------------------------------------------------------------------------- /hdl_checker/parsers/base_parser.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "Base source file parser" 18 | 19 | import abc 20 | import logging 21 | import os.path as p 22 | from typing import Any, Dict, Optional, Set 23 | 24 | from .elements.dependency_spec import ( 25 | BaseDependencySpec, 26 | ) # pylint: disable=unused-import 27 | from .elements.design_unit import tAnyDesignUnit # pylint: disable=unused-import 28 | 29 | from hdl_checker.path import Path 30 | from hdl_checker.types import FileType 31 | from hdl_checker.utils import HashableByKey, readFile, removeDuplicates 32 | 33 | _logger = logging.getLogger(__name__) 34 | 35 | 36 | class BaseSourceFile(HashableByKey): # pylint:disable=too-many-instance-attributes 37 | """ 38 | Parses and stores information about a source file such as design 39 | units it depends on and design units it provides 40 | """ 41 | 42 | __metaclass__ = abc.ABCMeta 43 | 44 | def __init__(self, filename): 45 | # type: (Path, ) -> None 46 | assert isinstance(filename, Path), "Invalid type: {}".format(filename) 47 | self.filename = filename 48 | self._cache = {} # type: Dict[str, Any] 49 | self._content = None # type: Optional[str] 50 | self._mtime = 0 # type: Optional[float] 51 | self.filetype = FileType.fromPath(self.filename) 52 | self._dependencies = None # type: Optional[Set[BaseDependencySpec]] 53 | self._design_units = None # type: Optional[Set[tAnyDesignUnit]] 54 | self._libraries = None 55 | 56 | def __jsonEncode__(self): 57 | """ 58 | Gets a dict that describes the current state of this object 59 | """ 60 | state = self.__dict__.copy() 61 | del state["_content"] 62 | del state["_design_units"] 63 | del state["_dependencies"] 64 | return state 65 | 66 | @classmethod 67 | def __jsonDecode__(cls, state): 68 | """ 69 | Returns an object of cls based on a given state 70 | """ 71 | obj = super(BaseSourceFile, cls).__new__(cls) 72 | obj.filename = state["filename"] 73 | obj.filetype = state["filetype"] 74 | obj._cache = state["_cache"] # pylint: disable=protected-access 75 | obj._content = None # pylint: disable=protected-access 76 | obj._mtime = state["_mtime"] # pylint: disable=protected-access 77 | obj._dependencies = None # pylint: disable=protected-access 78 | obj._design_units = None # pylint: disable=protected-access 79 | obj._libraries = state["_libraries"] # pylint: disable=protected-access 80 | 81 | return obj 82 | 83 | def __repr__(self): 84 | return "{}(filename={}, design_units={}, dependencies={})".format( 85 | self.__class__.__name__, 86 | self.filename, 87 | self._design_units, 88 | self._dependencies, 89 | ) 90 | 91 | @property 92 | def __hash_key__(self): 93 | return (self.filename, self._content) 94 | 95 | def _changed(self): 96 | # type: (...) -> Any 97 | """ 98 | Checks if the file changed based on the modification time 99 | provided by p.getmtime 100 | """ 101 | if not p.exists(str(self.filename)): 102 | return False 103 | return bool(self.getmtime() > self._mtime) # type: ignore 104 | 105 | def _clearCachesIfChanged(self): 106 | # type: () -> None 107 | """ 108 | Clears all the caches if the file has changed to force updating 109 | every parsed info 110 | """ 111 | if self._changed(): 112 | self._content = None 113 | self._dependencies = None 114 | self._design_units = None 115 | self._libraries = None 116 | self._cache = {} 117 | 118 | def getmtime(self): 119 | # type: () -> Optional[float] 120 | """ 121 | Gets file modification time as defined in p.getmtime 122 | """ 123 | if not p.exists(self.filename.name): 124 | return None 125 | return self.filename.mtime 126 | 127 | def getSourceContent(self): 128 | # type: (...) -> Any 129 | """ 130 | Cached version of the _getSourceContent method 131 | """ 132 | self._clearCachesIfChanged() 133 | 134 | if self._content is None: 135 | self._content = self._getSourceContent() 136 | self._mtime = self.getmtime() 137 | 138 | return self._content 139 | 140 | def _getSourceContent(self): 141 | # type: () -> str 142 | """ 143 | Method that can be overriden to change the contents of the file, like 144 | striping comments off. 145 | """ 146 | return readFile(str(self.filename)) 147 | 148 | def getDesignUnits(self): # type: () -> Set[tAnyDesignUnit] 149 | """ 150 | Cached version of the _getDesignUnits method 151 | """ 152 | if not p.exists(self.filename.name): 153 | return set() 154 | self._clearCachesIfChanged() 155 | if self._design_units is None: 156 | self._design_units = set(self._getDesignUnits()) 157 | 158 | return self._design_units 159 | 160 | def getDependencies(self): 161 | # type: () -> Set[BaseDependencySpec] 162 | """ 163 | Cached version of the _getDependencies method 164 | """ 165 | if not p.exists(self.filename.name): 166 | return set() 167 | 168 | self._clearCachesIfChanged() 169 | if self._dependencies is None: 170 | try: 171 | self._dependencies = set(self._getDependencies()) 172 | except: # pragma: no cover 173 | print("Failed to parse %s" % self.filename) 174 | _logger.exception("Failed to parse %s", self.filename) 175 | raise 176 | 177 | return self._dependencies 178 | 179 | def getLibraries(self): 180 | # type: (...) -> Any 181 | """ 182 | Cached version of the _getLibraries method 183 | """ 184 | if not p.exists(self.filename.name): 185 | return [] 186 | 187 | self._clearCachesIfChanged() 188 | if self._libraries is None: 189 | self._libraries = removeDuplicates(self._getLibraries()) 190 | 191 | return self._libraries 192 | 193 | @abc.abstractmethod 194 | def _getDesignUnits(self): 195 | """ 196 | Method that should implement the real parsing of the source file 197 | to find design units defined. Use the output of the getSourceContent 198 | method to avoid unnecessary I/O 199 | """ 200 | 201 | def _getLibraries(self): 202 | """ 203 | Parses the source file to find libraries required by the file 204 | """ 205 | return () 206 | 207 | @abc.abstractmethod 208 | def _getDependencies(self): 209 | """ 210 | Parses the source and returns a list of dictionaries that 211 | describe its dependencies 212 | """ 213 | -------------------------------------------------------------------------------- /hdl_checker/parsers/config_parser.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "Configuration file parser" 18 | 19 | # pylint: disable=useless-object-inheritance 20 | 21 | import logging 22 | import os.path as p 23 | import re 24 | from glob import glob 25 | from threading import RLock 26 | from typing import Any, Dict, Iterable, List, Tuple, Union 27 | 28 | from hdl_checker import exceptions 29 | from hdl_checker.path import Path 30 | from hdl_checker.types import BuildFlags, BuildFlagScope, FileType 31 | 32 | # pylint: disable=invalid-name 33 | _splitAtWhitespaces = re.compile(r"\s+").split 34 | _replaceCfgComments = re.compile(r"(\s*#.*|\n)").sub 35 | _configFileScan = re.compile( 36 | "|".join( 37 | [ 38 | r"^\s*(?P\w+)\s*(\[(?Pvhdl|verilog|systemverilog)\]|\s)*=\s*(?P.+)\s*$", 39 | r"^\s*(?P(vhdl|verilog|systemverilog))\s+" 40 | r"(?P\w+)\s+" 41 | r"(?P[^\s]+)\s*(?P.*)\s*", 42 | ] 43 | ), 44 | flags=re.I, 45 | ).finditer 46 | # pylint: enable=invalid-name 47 | 48 | 49 | def _extractSet(entry): # type: (str) -> BuildFlags 50 | """ 51 | Extract a list by splitting a string at whitespaces, removing 52 | empty values caused by leading/trailing/multiple whitespaces 53 | """ 54 | string = str(entry).strip() 55 | # Return an empty list if the string is empty 56 | if not string: 57 | return () 58 | return tuple(_splitAtWhitespaces(string)) 59 | 60 | 61 | class ConfigParser(object): 62 | """ 63 | Configuration info provider 64 | """ 65 | 66 | _list_parms = { 67 | "single_build_flags": BuildFlagScope.single, 68 | "global_build_flags": BuildFlagScope.all, 69 | "dependencies_build_flags": BuildFlagScope.dependencies, 70 | } 71 | 72 | _single_value_parms = ("builder",) 73 | _deprecated_parameters = ("target_dir",) 74 | 75 | _logger = logging.getLogger(__name__ + ".ConfigParser") 76 | 77 | def __init__(self, filename): # type: (Path) -> None 78 | self._logger.debug("Creating config parser for filename '%s'", filename) 79 | 80 | self._parms = {"builder": None} # type: Dict[str, Union[str, None]] 81 | 82 | self._flags = { 83 | FileType.vhdl: { 84 | BuildFlagScope.single: (), 85 | BuildFlagScope.all: (), 86 | BuildFlagScope.dependencies: (), 87 | }, 88 | FileType.verilog: { 89 | BuildFlagScope.single: (), 90 | BuildFlagScope.all: (), 91 | BuildFlagScope.dependencies: (), 92 | }, 93 | FileType.systemverilog: { 94 | BuildFlagScope.single: (), 95 | BuildFlagScope.all: (), 96 | BuildFlagScope.dependencies: (), 97 | }, 98 | } # type: Dict[FileType, Dict[BuildFlagScope, BuildFlags] ] 99 | 100 | self._sources = [] # type: List[Tuple[str, str, BuildFlags]] 101 | 102 | self.filename = filename 103 | 104 | self._timestamp = 0.0 105 | self._parse_lock = RLock() 106 | 107 | def _shouldParse(self): # type: () -> bool 108 | """ 109 | Checks if we should parse the configuration file 110 | """ 111 | return self.filename.mtime > self._timestamp 112 | 113 | def _updateTimestamp(self): 114 | # type: (...) -> Any 115 | """ 116 | Updates our timestamp with the configuration file 117 | """ 118 | self._timestamp = self.filename.mtime 119 | 120 | def isParsing(self): # type: () -> bool 121 | "Checks if parsing is ongoing in another thread" 122 | locked = not self._parse_lock.acquire(False) 123 | if not locked: 124 | self._parse_lock.release() 125 | return locked 126 | 127 | def _parseIfNeeded(self): 128 | # type: () -> None 129 | """ 130 | Locks accesses to parsed attributes and parses the configuration file 131 | """ 132 | with self._parse_lock: 133 | if self._shouldParse(): 134 | self._parse() 135 | 136 | def _parse(self): # type: () -> None 137 | """ 138 | Parse the configuration file without any previous checking 139 | """ 140 | self._logger.info("Parsing '%s'", self.filename) 141 | self._updateTimestamp() 142 | self._sources = [] 143 | for _line in open(self.filename.name, mode="rb").readlines(): 144 | line = _replaceCfgComments("", _line.decode(errors="ignore")) 145 | self._parseLine(line) 146 | 147 | def _parseLine(self, line): # type: (str) -> None 148 | """ 149 | Parses a line a calls the appropriate extraction methods 150 | """ 151 | for match in _configFileScan(line): 152 | groupdict = match.groupdict() 153 | self._logger.debug("match: '%s'", groupdict) 154 | if groupdict["parameter"] is not None: 155 | self._handleParsedParameter( 156 | groupdict["parameter"], groupdict["parm_lang"], groupdict["value"] 157 | ) 158 | else: 159 | for source_path in self._getSourcePaths(groupdict["path"]): 160 | self._sources.append( 161 | ( 162 | source_path, 163 | { 164 | "library": groupdict["library"], 165 | "flags": _extractSet(groupdict["flags"]), 166 | }, 167 | ) 168 | ) 169 | 170 | def _handleParsedParameter(self, parameter, lang, value): 171 | # type: (str, str, str) -> None 172 | """ 173 | Handles a parsed line that sets a parameter 174 | """ 175 | self._logger.debug( 176 | "Found parameter '%s' for '%s' with value '%s'", parameter, lang, value 177 | ) 178 | if parameter in self._deprecated_parameters: 179 | self._logger.debug("Ignoring deprecated parameter '%s'", parameter) 180 | elif parameter in self._single_value_parms: 181 | self._logger.debug("Handling '%s' as a single value", parameter) 182 | self._parms[parameter] = value 183 | elif parameter in self._list_parms: 184 | self._logger.debug("Handling '%s' as a list of values", parameter) 185 | self._flags[FileType(lang)][self._list_parms[parameter]] = _extractSet( 186 | value 187 | ) 188 | else: 189 | raise exceptions.UnknownParameterError(parameter) 190 | 191 | def _getSourcePaths(self, path): # type: (str) -> Iterable[str] 192 | """ 193 | Normalizes and handles absolute/relative paths 194 | """ 195 | source_path = p.normpath(p.expanduser(path)) 196 | # If the path to the source file was not absolute, we assume 197 | # it was relative to the config file base path 198 | if not p.isabs(source_path): 199 | fname_base_dir = p.dirname(self.filename.abspath) 200 | source_path = p.join(fname_base_dir, source_path) 201 | 202 | return glob(source_path) or [source_path] 203 | 204 | def parse(self): 205 | # type: (...) -> Dict[Any, Any] 206 | """ 207 | Parses the file if it hasn't been parsed before or if the config file 208 | has been changed 209 | """ 210 | self._parseIfNeeded() 211 | data = {"sources": self._sources} # type: Dict[Any, Any] 212 | 213 | builder_name = self._parms.get("builder", None) 214 | if builder_name is not None: 215 | data["builder"] = builder_name 216 | 217 | for filetype, flags in self._flags.items(): 218 | flags_dict = {} 219 | for scope in ( 220 | x for x in BuildFlagScope if x is not BuildFlagScope.source_specific 221 | ): 222 | flags_dict[scope.value] = flags[scope] 223 | data.update({filetype.name: {"flags": flags_dict}}) 224 | 225 | return data 226 | -------------------------------------------------------------------------------- /hdl_checker/parsers/elements/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | -------------------------------------------------------------------------------- /hdl_checker/parsers/elements/dependency_spec.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "Spec for a parsed dependency" 18 | 19 | from typing import Optional 20 | 21 | from .identifier import Identifier 22 | from .parsed_element import LocationList, ParsedElement # pylint: disable=unused-import 23 | 24 | from hdl_checker.path import Path # pylint: disable=unused-import 25 | 26 | 27 | class BaseDependencySpec(ParsedElement): 28 | "Placeholder for a source dependency" 29 | 30 | def __init__(self, owner, name, library=None, locations=None): 31 | # type: (Path, Identifier, Optional[Identifier], Optional[LocationList]) -> None 32 | assert isinstance(name, Identifier), "Incorrect arg: {}".format(name) 33 | assert library is None or isinstance( 34 | library, Identifier 35 | ), "Incorrect arg: {}".format(library) 36 | 37 | self._owner = owner 38 | self._library = library 39 | self._name = name 40 | super(BaseDependencySpec, self).__init__(locations) 41 | 42 | @property 43 | def owner(self): 44 | # type: (...) -> Path 45 | """ 46 | Path of the file that the dependency was found in 47 | """ 48 | return self._owner 49 | 50 | @property 51 | def name(self): 52 | # type: (...) -> Identifier 53 | """ 54 | Name of the design unit this dependency refers to 55 | """ 56 | return self._name 57 | 58 | @property 59 | def library(self): 60 | # type: (...) -> Optional[Identifier] 61 | """ 62 | Library, if any, this dependency was found. If None, should be 63 | equivalent to the library of the owner (aka 'work' library) 64 | """ 65 | return self._library 66 | 67 | def __len__(self): 68 | if self.library is None: 69 | return len(self.name) 70 | return len(self.name) + len(self.library) + 1 71 | 72 | @property 73 | def __hash_key__(self): 74 | return ( 75 | self.owner, 76 | self.library, 77 | self.name, 78 | super(BaseDependencySpec, self).__hash_key__, 79 | ) 80 | 81 | def __jsonEncode__(self): 82 | return { 83 | "owner": self.owner, 84 | "name": self.name, 85 | "library": self.library, 86 | "locations": tuple(self.locations), 87 | } 88 | 89 | @classmethod 90 | def __jsonDecode__(cls, state): 91 | """Returns an object of cls based on a given state""" 92 | return cls( 93 | library=state.pop("library"), 94 | name=state.pop("name"), 95 | owner=state.pop("owner"), 96 | locations=state.pop("locations"), 97 | ) 98 | 99 | def __repr__(self): 100 | return "{}(name={}, library={}, owner={}, locations={})".format( 101 | self.__class__.__name__, 102 | repr(self.name), 103 | repr(self.library), 104 | repr(self.owner), 105 | repr(self.locations), 106 | ) 107 | 108 | 109 | class RequiredDesignUnit(BaseDependencySpec): 110 | pass 111 | 112 | 113 | class IncludedPath(BaseDependencySpec): 114 | """ 115 | Special type of dependency for Verilog and SystemVerilog files. Its name is 116 | actually the string that the source is including. 117 | """ 118 | 119 | def __init__(self, owner, name, locations=None): 120 | # type: (Path, Identifier, Optional[LocationList]) -> None 121 | super(IncludedPath, self).__init__( 122 | owner=owner, name=name, library=None, locations=locations 123 | ) 124 | 125 | def __jsonEncode__(self): 126 | return { 127 | "owner": self.owner, 128 | "name": self.name, 129 | "locations": tuple(self.locations), 130 | } 131 | 132 | @classmethod 133 | def __jsonDecode__(cls, state): 134 | """Returns an object of cls based on a given state""" 135 | return cls( 136 | name=state.pop("name"), 137 | owner=state.pop("owner"), 138 | locations=state.pop("locations"), 139 | ) 140 | -------------------------------------------------------------------------------- /hdl_checker/parsers/elements/design_unit.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "Class defining a parsed design unit" 18 | 19 | import logging 20 | from typing import Optional, Union 21 | 22 | from .identifier import ( # pylint: disable=unused-import 23 | Identifier, 24 | VerilogIdentifier, 25 | VhdlIdentifier, 26 | ) 27 | from .parsed_element import LocationList, ParsedElement # pylint: disable=unused-import 28 | 29 | from hdl_checker.path import Path # pylint: disable=unused-import 30 | from hdl_checker.types import DesignUnitType # pylint: disable=unused-import 31 | 32 | _logger = logging.getLogger(__name__) 33 | 34 | 35 | class _DesignUnit(ParsedElement): 36 | """ 37 | Specifies a design unit (uses mostly VHDL nomenclature) 38 | """ 39 | 40 | def __init__(self, owner, type_, name, locations=None): 41 | # type: (Path, DesignUnitType, Identifier, Optional[LocationList]) -> None 42 | self._owner = owner 43 | self._type = type_ 44 | self._name = name 45 | 46 | super(_DesignUnit, self).__init__(locations) 47 | 48 | def __len__(self): 49 | return len(self.name) 50 | 51 | def __repr__(self): 52 | return '{}(name="{}", type={}, owner={}, locations={})'.format( 53 | self.__class__.__name__, 54 | repr(self.name), 55 | repr(self.type_), 56 | repr(self.owner), 57 | self.locations, 58 | ) 59 | 60 | def __str__(self): 61 | return "{}(name='{}', type={}, owner='{}')".format( 62 | self.__class__.__name__, self.name.display_name, self.type_, str(self.owner) 63 | ) 64 | 65 | def __jsonEncode__(self): 66 | return { 67 | "owner": self.owner, 68 | "type_": self.type_, 69 | "name": self.name, 70 | "locations": tuple(self.locations), 71 | } 72 | 73 | @classmethod 74 | def __jsonDecode__(cls, state): 75 | """Returns an object of cls based on a given state""" 76 | # pylint: disable=protected-access 77 | return cls( 78 | state.pop("owner"), 79 | state.pop("type_"), 80 | state.pop("name"), 81 | state.pop("locations", None), 82 | ) 83 | 84 | @property 85 | def owner(self): 86 | # type: () -> Path 87 | "Owner of the design unit" 88 | return self._owner 89 | 90 | @property 91 | def type_(self): 92 | # type: () -> DesignUnitType 93 | "Design unit type" 94 | return self._type 95 | 96 | @property 97 | def name(self): 98 | # type: () -> Identifier 99 | "Design unit name" 100 | return self._name 101 | 102 | @property 103 | def __hash_key__(self): 104 | return ( 105 | self.owner, 106 | self.type_, 107 | self.name, 108 | super(_DesignUnit, self).__hash_key__, 109 | ) 110 | 111 | 112 | class VhdlDesignUnit(_DesignUnit): 113 | """ 114 | Specifies a design unit whose name is case insensitive 115 | """ 116 | 117 | def __init__(self, owner, type_, name, locations=None): 118 | # type: (Path, DesignUnitType, str, Optional[LocationList]) -> None 119 | super(VhdlDesignUnit, self).__init__( 120 | owner=owner, type_=type_, name=VhdlIdentifier(name), locations=locations 121 | ) 122 | 123 | 124 | class VerilogDesignUnit(_DesignUnit): 125 | """ 126 | Specifies a design unit whose name is case sensitive 127 | """ 128 | 129 | def __init__(self, owner, type_, name, locations=None): 130 | # type: (Path, DesignUnitType, str, Optional[LocationList]) -> None 131 | super(VerilogDesignUnit, self).__init__( 132 | owner=owner, type_=type_, name=VerilogIdentifier(name), locations=locations 133 | ) 134 | 135 | 136 | tAnyDesignUnit = Union[VhdlDesignUnit, VerilogDesignUnit] 137 | -------------------------------------------------------------------------------- /hdl_checker/parsers/elements/identifier.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "VHDL, Verilog or SystemVerilog identifier and comparisons between them" 18 | 19 | # pylint: disable=useless-object-inheritance 20 | 21 | 22 | class Identifier(object): 23 | """ 24 | VHDL, Verilog or SystemVerilog identifier to make it easier to handle case 25 | and comparisons between them 26 | """ 27 | 28 | def __init__(self, name, case_sensitive=False): 29 | # type: (str, bool) -> None 30 | self.case_sensitive = case_sensitive 31 | self._display_name = str(name) 32 | self._name = self._display_name.lower() 33 | 34 | @property 35 | def name(self): 36 | "Normalized identifier name" 37 | return self._name 38 | 39 | @property 40 | def display_name(self): 41 | "Identifier name as given when creating the object" 42 | return self._display_name 43 | 44 | def __jsonEncode__(self): 45 | return {"name": self.display_name, "case_sensitive": self.case_sensitive} 46 | 47 | @classmethod 48 | def __jsonDecode__(cls, state): 49 | return cls(name=state.pop("name"), case_sensitive=state.pop("case_sensitive")) 50 | 51 | def __hash__(self): 52 | return hash(self.name) 53 | 54 | def __str__(self): 55 | return self.display_name 56 | 57 | def __len__(self): 58 | return len(self.display_name) 59 | 60 | def __repr__(self): 61 | if self.name == self.display_name: 62 | return "{}({})".format(self.__class__.__name__, repr(self.name)) 63 | return "{}({}, display_name={})".format( 64 | self.__class__.__name__, repr(self.name), repr(self.display_name) 65 | ) 66 | 67 | def __eq__(self, other): 68 | """Overrides the default implementation""" 69 | 70 | try: 71 | if self.case_sensitive and other.case_sensitive: 72 | return self.display_name == other.display_name 73 | return self.name == other.name 74 | except AttributeError: 75 | pass 76 | 77 | return NotImplemented # pragma: no cover 78 | 79 | def __ne__(self, other): # pragma: no cover 80 | """Overrides the default implementation (unnecessary in Python 3)""" 81 | result = self.__eq__(other) 82 | 83 | if result is not NotImplemented: 84 | return not result 85 | 86 | return NotImplemented 87 | 88 | 89 | class VhdlIdentifier(Identifier): 90 | "Equivalent of Identifier(name, case_sensitive=False)" 91 | 92 | def __init__(self, name): 93 | # type: (str, ) -> None 94 | super(VhdlIdentifier, self).__init__(name=name, case_sensitive=False) 95 | 96 | @classmethod 97 | def __jsonDecode__(cls, state): 98 | return cls(name=state.pop("name")) 99 | 100 | 101 | class VerilogIdentifier(Identifier): 102 | "Equivalent of Identifier(name, case_sensitive=True)" 103 | 104 | def __init__(self, name): 105 | # type: (str, ) -> None 106 | super(VerilogIdentifier, self).__init__(name=name, case_sensitive=True) 107 | 108 | @classmethod 109 | def __jsonDecode__(cls, state): 110 | return cls(name=state.pop("name")) 111 | -------------------------------------------------------------------------------- /hdl_checker/parsers/elements/parsed_element.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | 18 | import logging 19 | from typing import Iterable, Optional, Set 20 | import abc 21 | 22 | from hdl_checker.types import Location 23 | from hdl_checker.utils import HashableByKey 24 | 25 | _logger = logging.getLogger(__name__) 26 | 27 | LocationList = Iterable[Location] 28 | 29 | 30 | class ParsedElement(HashableByKey): 31 | __metaclass__ = abc.ABCMeta 32 | 33 | def __init__(self, locations=None): 34 | # type: (Optional[LocationList]) -> None 35 | set_of_locations = set() # type: Set[Location] 36 | for line_number, column_number in locations or []: 37 | set_of_locations.add( 38 | Location( 39 | None if line_number is None else int(line_number), 40 | None if column_number is None else int(column_number), 41 | ) 42 | ) 43 | 44 | self._locations = tuple(set_of_locations) 45 | 46 | @property 47 | def locations(self): 48 | return self._locations 49 | 50 | @property 51 | def __hash_key__(self): 52 | return (self.locations,) 53 | 54 | @abc.abstractmethod 55 | def __len__(self): 56 | # type: (...) -> int 57 | """ 58 | len(self) should return the length the parsed element uses on the text. 59 | It will be used to calculate an end position for it and allow checking 60 | if a given location is within the element's text 61 | """ 62 | 63 | def includes(self, line, column): 64 | # type: (int, int) -> bool 65 | name_length = len(self) 66 | 67 | for location in self.locations: 68 | if line != location.line: 69 | continue 70 | 71 | if location.column <= column < location.column + name_length: 72 | return True 73 | return False 74 | -------------------------------------------------------------------------------- /hdl_checker/parsers/verilog_parser.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "VHDL source file parser" 18 | 19 | import logging 20 | import re 21 | import string 22 | from typing import Any, Generator, Iterable, List, Tuple, Type 23 | 24 | from .elements.dependency_spec import ( 25 | BaseDependencySpec, 26 | IncludedPath, 27 | RequiredDesignUnit, 28 | ) 29 | from .elements.design_unit import VerilogDesignUnit 30 | from .elements.parsed_element import Location 31 | 32 | from hdl_checker.parsers.base_parser import BaseSourceFile 33 | from hdl_checker.parsers.elements.identifier import VerilogIdentifier 34 | from hdl_checker.types import DesignUnitType, FileType 35 | from hdl_checker.utils import readFile 36 | 37 | _logger = logging.getLogger(__name__) 38 | 39 | _VERILOG_IDENTIFIER = "|".join( 40 | [r"[a-zA-Z_][a-zA-Z0-9_$]+", r"\\[%s]+(?=\s)" % string.printable.replace(" ", "")] 41 | ) 42 | 43 | _COMMENT = r"(?:\/\*.*?\*\/|//[^(?:\r\n?|\n)]*)" 44 | 45 | 46 | # Design unit scanner 47 | _DESIGN_UNITS = re.compile( 48 | "|".join( 49 | [ 50 | r"(?<=\bmodule\b)\s*(?P%s)" % _VERILOG_IDENTIFIER, 51 | r"(?<=\bpackage\b)\s*(?P%s)" % _VERILOG_IDENTIFIER, 52 | _COMMENT, 53 | ] 54 | ), 55 | flags=re.DOTALL, 56 | ) 57 | 58 | _DEPENDENCIES = re.compile( 59 | "|".join( 60 | [ 61 | r"(?P\b{0})\s*::\s*(?:{0}|\*)".format(_VERILOG_IDENTIFIER), 62 | r"(\bvirtual\b)?\s*\bclass\s+(?:static|automatic)?(?P\b{0})".format( 63 | _VERILOG_IDENTIFIER 64 | ), 65 | r"(?<=`include\b)\s*\"(?P.*?)\"", 66 | _COMMENT, 67 | ] 68 | ), 69 | flags=re.DOTALL, 70 | ) 71 | 72 | 73 | class VerilogParser(BaseSourceFile): 74 | """ 75 | Parses and stores information about a Verilog or SystemVerilog 76 | source file 77 | """ 78 | 79 | def _getSourceContent(self): 80 | # type: (...) -> Any 81 | # Remove multiline comments 82 | content = readFile(str(self.filename)) 83 | return content 84 | # lines = _COMMENT.sub("", content) 85 | # return re.sub(r"\r\n?|\n", " ", lines, flags=re.S) 86 | 87 | def _iterDesignUnitMatches(self): 88 | # type: (...) -> Any 89 | """ 90 | Iterates over the matches of _DESIGN_UNITS against 91 | source's lines 92 | """ 93 | content = self.getSourceContent() 94 | lines = content.split("\n") 95 | for match in _DESIGN_UNITS.finditer(self.getSourceContent()): 96 | start = match.start() 97 | start_line = content[:start].count("\n") 98 | 99 | total_chars_to_line_with_match = len("\n".join(lines[:start_line])) 100 | start_char = match.start() - total_chars_to_line_with_match 101 | 102 | yield match.groupdict(), {Location(start_line, start_char)} 103 | 104 | def _getDependencies(self): # type: () -> Iterable[BaseDependencySpec] 105 | text = self.getSourceContent() 106 | 107 | match_groups = [ 108 | ("include", IncludedPath) 109 | ] # type: List[Tuple[str, Type[BaseDependencySpec]]] 110 | 111 | # Only SystemVerilog has imports or classes 112 | if self.filetype is FileType.systemverilog: 113 | match_groups += [ 114 | ("package", RequiredDesignUnit), 115 | ("class", RequiredDesignUnit), 116 | ] 117 | 118 | for match in _DEPENDENCIES.finditer(text): 119 | for match_group, klass in match_groups: 120 | name = match.groupdict().get(match_group, None) 121 | # package 'std' seems to be built-in. Need to have a look a 122 | # this if include_name is not None and include_name != 'std': 123 | if match_group == "package" and name == "std": 124 | continue 125 | 126 | # package 'std' seems to be built-in. Need to have a look a this 127 | if name is None: 128 | continue 129 | 130 | line_number = text[: match.end()].count("\n") 131 | column_number = len(text[: match.start()].split("\n")[-1]) 132 | 133 | yield klass( 134 | owner=self.filename, 135 | name=VerilogIdentifier(name), 136 | locations=(Location(line_number, column_number),), 137 | ) 138 | break 139 | 140 | def _getDesignUnits(self): # type: () -> Generator[VerilogDesignUnit, None, None] 141 | for match, locations in self._iterDesignUnitMatches(): 142 | if match["module_name"] is not None: 143 | yield VerilogDesignUnit( 144 | owner=self.filename, 145 | name=match["module_name"], 146 | type_=DesignUnitType.entity, 147 | locations=locations, 148 | ) 149 | if match["package_name"] is not None: 150 | yield VerilogDesignUnit( 151 | owner=self.filename, 152 | name=match["package_name"], 153 | type_=DesignUnitType.package, 154 | locations=locations, 155 | ) 156 | -------------------------------------------------------------------------------- /hdl_checker/parsers/vhdl_parser.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "VHDL source file parser" 18 | 19 | import re 20 | from typing import Any, Dict, Generator, Iterable, Optional, Set, Tuple, Union 21 | 22 | from .elements.dependency_spec import RequiredDesignUnit 23 | from .elements.design_unit import VhdlDesignUnit 24 | from .elements.identifier import VhdlIdentifier 25 | from .elements.parsed_element import Location 26 | 27 | from hdl_checker.parsers.base_parser import BaseSourceFile 28 | from hdl_checker.types import DesignUnitType 29 | 30 | # Design unit scanner 31 | _DESIGN_UNITS = re.compile( 32 | "|".join( 33 | [ 34 | r"(?<=\bpackage\b)\s+(?P\w+)(?=\s+is\b)", 35 | r"(?<=\bentity\b)\s+(?P\w+)(?=\s+is\b)", 36 | r"(?<=\blibrary)\s+(?P[\w,\s]+)\b", 37 | r"(?<=\bcontext\b)\s+(?P\w+)(?=\s+is\b)", 38 | r"(?P\s*--.*)", 39 | ] 40 | ), 41 | flags=re.MULTILINE | re.IGNORECASE, 42 | ) 43 | 44 | _LIBRARIES = re.compile( 45 | r"(?:\blibrary\s+(?P[a-z]\w*(?:\s*,\s*[a-z]\w*){0,})\s*;)|(?:\s*--.*)", 46 | flags=re.MULTILINE | re.IGNORECASE, 47 | ) 48 | 49 | _PACKAGE_BODY = re.compile( 50 | r"\bpackage\s+body\s+(?P\w+)\s+is\b" 51 | r"|" 52 | r"(?P\s*--.*)", 53 | flags=re.MULTILINE | re.IGNORECASE, 54 | ) 55 | 56 | _LIBRARY_USES = re.compile( 57 | r"(?:(?P\b\w+)\s*\.\s*(?P\b\w+\w+))|(?:\s*--.*)", flags=re.I 58 | ) 59 | IncompleteDependency = Dict[str, Union[str, Set[Any]]] 60 | 61 | 62 | class _PartialDependency(object): # pylint: disable=useless-object-inheritance 63 | """ 64 | Stores dependencies definitions to create immutable objects later on 65 | """ 66 | 67 | def __init__(self): 68 | # type: (...) -> None 69 | self._keys = set() # type: Set[int] 70 | self._libraries = {} # type: Dict[int, Optional[VhdlIdentifier]] 71 | self._units = {} # type: Dict[int, VhdlIdentifier] 72 | self._locations = {} # type: Dict[int, Set[Location]] 73 | 74 | def add(self, library, unit, line, column): 75 | # type: (str, str, int, int) -> None 76 | """ 77 | Adds a dependency definition to the list 78 | """ 79 | _library = None if library.lower() == "work" else VhdlIdentifier(library) 80 | _unit = VhdlIdentifier(unit) 81 | 82 | key = hash((_library, _unit)) 83 | if key not in self._keys: 84 | self._keys.add(key) 85 | self._libraries[key] = _library 86 | self._units[key] = _unit 87 | self._locations[key] = set() 88 | 89 | self._locations[key].add(Location(line, column)) 90 | 91 | def items(self): 92 | # type: () -> Iterable[Tuple[Optional[VhdlIdentifier], VhdlIdentifier, Set[Location]]] 93 | """ 94 | Returns items added previously 95 | """ 96 | for key in self._keys: 97 | yield self._libraries[key], self._units[key], self._locations[key] 98 | 99 | 100 | class VhdlParser(BaseSourceFile): 101 | """ 102 | Parses and stores information about a source file such as design 103 | units it depends on and design units it provides 104 | """ 105 | 106 | def _iterDesignUnitMatches(self): 107 | # type: (...) -> Any 108 | """ 109 | Iterates over the matches of _DESIGN_UNITS against 110 | source's lines 111 | """ 112 | content = self.getSourceContent() 113 | lines = content.split("\n") 114 | 115 | for match in _DESIGN_UNITS.finditer(content): 116 | start = match.start() 117 | start_line = content[:start].count("\n") 118 | 119 | total_chars_to_line_with_match = len("\n".join(lines[:start_line])) 120 | start_char = match.start() - total_chars_to_line_with_match 121 | 122 | yield match.groupdict(), {Location(start_line, start_char)} 123 | 124 | def _getDependencies(self): # type: () -> Generator[RequiredDesignUnit, None, None] 125 | library_names = {x.lower() for x in self.getLibraries()} 126 | library_names.add("work") 127 | 128 | dependencies = _PartialDependency() 129 | 130 | text = self.getSourceContent() 131 | 132 | for match in _LIBRARY_USES.finditer(text): 133 | if match.groupdict()["library"] is None: 134 | continue 135 | 136 | # Strip extra whitespaces and line breaks here instead of inside 137 | # the regex to allow using a single finditer call 138 | library = match.groupdict()["library"] 139 | 140 | if library.lower() not in library_names: 141 | continue 142 | 143 | unit = match.groupdict()["unit"] 144 | 145 | line_number = text[: match.end()].count("\n") 146 | column_number = len(text[: match.start()].split("\n")[-1]) 147 | 148 | dependencies.add(library, unit, line_number, column_number) 149 | 150 | # Done parsing, won't add any more locations, so generate the specs 151 | for _library, name, locations in dependencies.items(): 152 | # Remove references to 'work' (will treat library=None as work, 153 | # which also means not set in case of packages) 154 | yield RequiredDesignUnit( 155 | owner=self.filename, name=name, library=_library, locations=locations 156 | ) 157 | 158 | # Package bodies need a package declaration; include those as 159 | # dependencies as well 160 | for match in _PACKAGE_BODY.finditer(self.getSourceContent()): 161 | package_body_name = match.groupdict()["package_body_name"] 162 | if package_body_name is None: 163 | continue 164 | line_number = int(text[: match.end()].count("\n")) 165 | column_number = len(text[: match.start()].split("\n")[-1]) 166 | 167 | yield RequiredDesignUnit( 168 | owner=self.filename, 169 | name=VhdlIdentifier(package_body_name), 170 | library=None, 171 | locations={Location(line_number, column_number)}, 172 | ) 173 | 174 | def _getLibraries(self): 175 | # type: (...) -> Any 176 | """ 177 | Parses the source file to find design units and dependencies 178 | """ 179 | libs = set() # type: Set[str] 180 | 181 | for match in _LIBRARIES.finditer(self.getSourceContent()): 182 | for group in match.groups(): 183 | if group is not None: 184 | libs = libs.union(set(map(str.strip, str(group).split(",")))) 185 | 186 | # Replace references of 'work' for the actual library name 187 | if "work" in libs: 188 | libs = libs - {"work"} 189 | 190 | return libs 191 | 192 | def _getDesignUnits(self): # type: () -> Generator[VhdlDesignUnit, None, None] 193 | """ 194 | Parses the source file to find design units and dependencies 195 | """ 196 | 197 | for match, locations in self._iterDesignUnitMatches(): 198 | if match["package_name"] is not None: 199 | yield VhdlDesignUnit( 200 | owner=self.filename, 201 | name=match["package_name"], 202 | type_=DesignUnitType.package, 203 | locations=locations, 204 | ) 205 | 206 | elif match["entity_name"] is not None: 207 | yield VhdlDesignUnit( 208 | owner=self.filename, 209 | name=match["entity_name"], 210 | type_=DesignUnitType.entity, 211 | locations=locations, 212 | ) 213 | elif match["context_name"] is not None: 214 | yield VhdlDesignUnit( 215 | owner=self.filename, 216 | name=match["context_name"], 217 | type_=DesignUnitType.context, 218 | locations=locations, 219 | ) 220 | -------------------------------------------------------------------------------- /hdl_checker/path.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "Path helper class to speed up comparing different paths" 18 | 19 | # pylint: disable=useless-object-inheritance 20 | 21 | import logging 22 | from os import path as p 23 | from os import stat 24 | from typing import Union 25 | 26 | import six 27 | 28 | _logger = logging.getLogger(__name__) 29 | 30 | 31 | class Path(object): 32 | "Path helper class to speed up comparing different paths" 33 | 34 | def __init__(self, name, base_path=None): 35 | # type: (Union[Path, str], Union[Path, str, None]) -> None 36 | assert isinstance( 37 | name, (Path, six.string_types) 38 | ), "Invalid type for path: {} ({})".format(name, type(name)) 39 | 40 | if p.isabs(str(name)) or base_path is None: 41 | _name = name 42 | else: 43 | _name = p.join(str(base_path), str(name)) 44 | self._name = p.normpath(str(_name)) 45 | 46 | @property 47 | def mtime(self): 48 | # type: () -> float 49 | """ 50 | Equivalent to os.path.getmtime(self.name) 51 | """ 52 | return p.getmtime(self.name) 53 | 54 | @property 55 | def abspath(self): 56 | # type: () -> str 57 | """ 58 | Equivalent to os.path.abspath(self.name) 59 | """ 60 | return p.abspath(self.name) 61 | 62 | @property 63 | def basename(self): 64 | # type: () -> str 65 | """ 66 | Equivalent to os.path.basename(self.name) 67 | """ 68 | return p.basename(self.name) 69 | 70 | @property 71 | def dirname(self): 72 | # type: () -> str 73 | """ 74 | Equivalent to os.path.dirname(self.name) 75 | """ 76 | return p.dirname(self.name) 77 | 78 | @property 79 | def name(self): 80 | """ 81 | Absolute path, either the path passed to the constructor or the path 82 | prepended with base_path. In the second case, it's up to the caller to 83 | ensure an absolute path can be constructed; no exception or warning is 84 | thrown. 85 | """ 86 | return self._name 87 | 88 | def __str__(self): 89 | return self.name 90 | 91 | def __repr__(self): 92 | # type: () -> str 93 | return "{}({})".format(self.__class__.__name__, repr(self.name)) 94 | 95 | @property 96 | def stat(self): 97 | """ 98 | Equivalent to os.path.stat(self.name) 99 | """ 100 | return stat(self.name) 101 | 102 | def __hash__(self): 103 | return hash(self.name) 104 | 105 | def __eq__(self, other): 106 | """Overrides the default implementation""" 107 | try: 108 | # Same absolute paths mean the point to the same file. Prefer this 109 | # to avoid calling os.stat all the time 110 | if self.abspath == other.abspath: 111 | return True 112 | return p.samestat(self.stat, other.stat) 113 | except (AttributeError, FileNotFoundError): 114 | return False 115 | 116 | return NotImplemented # pragma: no cover 117 | 118 | def __ne__(self, other): # pragma: no cover 119 | """Overrides the default implementation (unnecessary in Python 3)""" 120 | result = self.__eq__(other) 121 | 122 | if result is NotImplemented: 123 | return NotImplemented 124 | 125 | return not result 126 | 127 | def __jsonEncode__(self): 128 | """ 129 | Gets a dict that describes the current state of this object 130 | """ 131 | return {"name": self.name} 132 | 133 | @classmethod 134 | def __jsonDecode__(cls, state): 135 | """Returns an object of cls based on a given state""" 136 | 137 | obj = super(Path, cls).__new__(cls) 138 | obj._name = state["name"] # pylint: disable=protected-access 139 | 140 | return obj 141 | 142 | def endswith(self, other): 143 | # type: (str) -> bool 144 | """ 145 | Checks if the paths end with the same suffix 146 | """ 147 | # Split the path at the path separator to compare the appropriate 148 | # part 149 | ref = p.normpath(other).split(p.sep) 150 | return self.name.split(p.sep)[-len(ref) :] == ref 151 | 152 | 153 | class TemporaryPath(Path): 154 | """ 155 | Class made just to differentiate a path from a temporary path created to 156 | dump a source's content 157 | """ 158 | -------------------------------------------------------------------------------- /hdl_checker/serialization.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "Serialization specifics" 18 | 19 | import json 20 | import logging 21 | 22 | from hdl_checker.builders.fallback import Fallback 23 | from hdl_checker.builders.ghdl import GHDL 24 | from hdl_checker.builders.msim import MSim 25 | from hdl_checker.builders.xvhdl import XVHDL 26 | from hdl_checker.database import Database 27 | from hdl_checker.parsers.elements.dependency_spec import ( 28 | IncludedPath, 29 | RequiredDesignUnit, 30 | ) 31 | from hdl_checker.parsers.elements.design_unit import VerilogDesignUnit, VhdlDesignUnit 32 | from hdl_checker.parsers.elements.identifier import ( 33 | Identifier, 34 | VerilogIdentifier, 35 | VhdlIdentifier, 36 | ) 37 | from hdl_checker.parsers.verilog_parser import VerilogParser 38 | from hdl_checker.parsers.vhdl_parser import VhdlParser 39 | from hdl_checker.path import Path, TemporaryPath 40 | from hdl_checker.types import FileType 41 | 42 | _logger = logging.getLogger(__name__) 43 | 44 | # Maps class names added by the decoder to the actual class on Python side to 45 | # recreate an object 46 | CLASS_MAP = { 47 | "Database": Database, 48 | "RequiredDesignUnit": RequiredDesignUnit, 49 | "IncludedPath": IncludedPath, 50 | "Fallback": Fallback, 51 | "FileType": FileType, 52 | "GHDL": GHDL, 53 | "Identifier": Identifier, 54 | "MSim": MSim, 55 | "Path": Path, 56 | "TemporaryPath": TemporaryPath, 57 | "VerilogIdentifier": VerilogIdentifier, 58 | "VerilogParser": VerilogParser, 59 | "VhdlDesignUnit": VhdlDesignUnit, 60 | "VerilogDesignUnit": VerilogDesignUnit, 61 | "VhdlIdentifier": VhdlIdentifier, 62 | "VhdlParser": VhdlParser, 63 | "XVHDL": XVHDL, 64 | } 65 | 66 | 67 | class StateEncoder(json.JSONEncoder): 68 | """ 69 | Custom encoder that handles hdl_checker classes 70 | """ 71 | 72 | def default(self, o): # pylint: disable=method-hidden 73 | if hasattr(o, "__jsonEncode__"): 74 | dct = o.__jsonEncode__() 75 | # Set a __class__ entry into the dict so we can use it to get from 76 | # CLASS_MAP when recreating the object 77 | prev = dct.get("__class__", None) 78 | if prev is not None and o.__class__.__name__ != prev: 79 | _logger.warning( 80 | "Class has been set to %s, will NOT overwrite it with %s!", 81 | prev, 82 | o.__class__.__name__, 83 | ) 84 | else: 85 | dct["__class__"] = o.__class__.__name__ 86 | # _logger.debug("object: %s, result:\n%s", repr(o), pformat(dct)) 87 | return dct 88 | # Let the base class default method raise the TypeError 89 | try: 90 | return json.JSONEncoder.default(self, o) 91 | except: 92 | _logger.fatal("object: %s, type=%s", o, type(o)) 93 | raise 94 | 95 | 96 | def jsonObjectHook(dict_): 97 | """ 98 | json hook for decoding entries added the StateEncoder back to Python 99 | objects 100 | """ 101 | if "__class__" not in dict_: 102 | return dict_ 103 | 104 | cls_name = dict_["__class__"] 105 | cls = CLASS_MAP.get(cls_name, None) 106 | assert cls is not None, "We should handle {}".format(cls_name) 107 | 108 | try: 109 | obj = cls.__jsonDecode__(dict_) 110 | except: # pragma: no cover 111 | _logger.error("Something went wrong, cls_name: %s => %s", cls_name, cls) 112 | raise 113 | return obj 114 | -------------------------------------------------------------------------------- /hdl_checker/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This file is part of HDL Checker. 3 | # 4 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 5 | # 6 | # HDL Checker is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # HDL Checker is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with HDL Checker. If not, see . 18 | "HDL Checker server launcher" 19 | 20 | # PYTHON_ARGCOMPLETE_OK 21 | 22 | import argparse 23 | import logging 24 | import os 25 | import sys 26 | from threading import Timer 27 | 28 | import six 29 | 30 | from hdl_checker import __version__ as version 31 | from hdl_checker import handlers, lsp 32 | from hdl_checker.utils import ( 33 | getTemporaryFilename, 34 | isProcessRunning, 35 | setupLogging, 36 | terminateProcess, 37 | ) 38 | 39 | _logger = logging.getLogger(__name__) 40 | 41 | 42 | def parseArguments(): 43 | "Argument parser for standalone hdl_checker" 44 | 45 | parser = argparse.ArgumentParser() 46 | 47 | # Options 48 | parser.add_argument("--host", action="store", help="[HTTP] Host to serve") 49 | parser.add_argument("--port", action="store", type=int, help="[HTTP] Port to serve") 50 | parser.add_argument( 51 | "--lsp", 52 | action="store_true", 53 | default=False, 54 | help="Starts the server in LSP mode. Defaults to false", 55 | ) 56 | 57 | parser.add_argument( 58 | "--attach-to-pid", 59 | action="store", 60 | type=int, 61 | help="[HTTP, LSP] Stops the server if given PID is not active", 62 | ) 63 | parser.add_argument("--log-level", action="store", help="[HTTP, LSP] Logging level") 64 | parser.add_argument( 65 | "--log-stream", 66 | action="store", 67 | help="[HTTP, LSP] Log file, defaults to stdout when in HTTP or a " 68 | "temporary file named hdl_checker_log_pid.log when in LSP mode. " 69 | "Use NONE to disable logging altogether", 70 | ) 71 | 72 | parser.add_argument( 73 | "--stdout", 74 | action="store", 75 | help="[HTTP] File to redirect stdout to. Defaults to a temporary file " 76 | "named hdl_checker_stdout_pid.log", 77 | ) 78 | parser.add_argument( 79 | "--stderr", 80 | action="store", 81 | help="[HTTP] File to redirect stdout to. Defaults to a temporary file " 82 | "named hdl_checker_stderr_pid.log. " 83 | "Use NONE to disable redirecting stderr altogether", 84 | ) 85 | 86 | parser.add_argument( 87 | "--version", 88 | "-V", 89 | action="store_true", 90 | help="Prints hdl_checker version and exit", 91 | ) 92 | 93 | try: 94 | import argcomplete # type: ignore 95 | 96 | argcomplete.autocomplete(parser) 97 | except ImportError: # pragma: no cover 98 | pass 99 | 100 | args = parser.parse_args() 101 | 102 | if args.version: 103 | sys.stdout.write("%s\n" % version) 104 | sys.exit(0) 105 | 106 | if args.lsp: 107 | args.host = None 108 | args.port = None 109 | else: 110 | args.host = args.host or "localhost" 111 | args.port = args.port or 50000 112 | args.log_stream = args.log_stream or sys.stdout 113 | 114 | # If not set, create a temporary file safely so there's no clashes 115 | if args.log_stream == "NONE": 116 | args.log_stream = None 117 | else: 118 | args.log_stream = args.log_stream or getTemporaryFilename("log") 119 | 120 | if args.stderr == "NONE": 121 | args.stderr = None 122 | else: 123 | args.stderr = args.stderr or getTemporaryFilename("stderr") 124 | 125 | args.log_level = args.log_level or logging.INFO 126 | 127 | return args 128 | 129 | 130 | # Copied from ycmd! 131 | def openForStdHandle(filepath): 132 | """ 133 | Returns a file object that can be used to replace sys.stdout or 134 | sys.stderr 135 | """ 136 | # Need to open the file in binary mode on py2 because of bytes vs unicode. 137 | # If we open in text mode (default), then third-party code that uses `print` 138 | # (we're replacing sys.stdout!) with an `str` object on py2 will cause 139 | # tracebacks because text mode insists on unicode objects. (Don't forget, 140 | # `open` is actually `io.open` because of future builtins.) 141 | # Since this function is used for logging purposes, we don't want the output 142 | # to be delayed. This means no buffering for binary mode and line buffering 143 | # for text mode. See https://docs.python.org/2/library/io.html#io.open 144 | if six.PY2: 145 | return open(filepath, mode="wb", buffering=0) 146 | return open(filepath, mode="w", buffering=1) 147 | 148 | 149 | def _setupPipeRedirection(stdout, stderr): # pragma: no cover 150 | "Redirect stdout and stderr to files" 151 | if stdout is not None: 152 | sys.stdout = openForStdHandle(stdout) 153 | if stderr is not None: 154 | sys.stderr = openForStdHandle(stderr) 155 | 156 | 157 | def _binaryStdio(): # pragma: no cover 158 | """ 159 | (from https://github.com/palantir/python-language-server) 160 | 161 | This seems to be different for Window/Unix Python2/3, so going by: 162 | https://stackoverflow.com/questions/2850893/reading-binary-data-from-stdin 163 | """ 164 | 165 | if six.PY3: 166 | # pylint: disable=no-member 167 | stdin, stdout = sys.stdin.buffer, sys.stdout.buffer 168 | else: 169 | # Python 2 on Windows opens sys.stdin in text mode, and 170 | # binary data that read from it becomes corrupted on \r\n 171 | if sys.platform == "win32": 172 | # set sys.stdin to binary mode 173 | # pylint: disable=no-member,import-error 174 | import msvcrt 175 | 176 | msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY) 177 | msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY) 178 | stdin, stdout = sys.stdin, sys.stdout 179 | 180 | return stdin, stdout 181 | 182 | 183 | def run(args): 184 | """ 185 | Import modules and tries to start a hdl_checker server 186 | """ 187 | # LSP will use stdio to communicate 188 | _setupPipeRedirection(None if args.lsp else args.stdout, args.stderr) 189 | 190 | if args.log_stream: 191 | setupLogging(args.log_stream, args.log_level) 192 | 193 | globals()["_logger"] = logging.getLogger(__name__) 194 | 195 | def _attachPids(source_pid, target_pid): 196 | "Monitors if source_pid is alive. If not, terminate target_pid" 197 | 198 | def _watchPidWrapper(): 199 | "PID attachment monitor" 200 | try: 201 | if isProcessRunning(source_pid): 202 | Timer(1, _watchPidWrapper).start() 203 | else: 204 | _logger.warning("Process %d is not running anymore", source_pid) 205 | terminateProcess(target_pid) 206 | except (TypeError, AttributeError): # pragma: no cover 207 | return 208 | 209 | _logger.debug("Setting up PID attachment from %s to %s", source_pid, target_pid) 210 | 211 | Timer(2, _watchPidWrapper).start() 212 | 213 | _logger.info( 214 | "Starting server. Our PID is %s, %s. Version string for hdl_checker is '%s'", 215 | os.getpid(), 216 | "no parent PID to attach to" 217 | if args.attach_to_pid is None 218 | else "our parent is %s" % args.attach_to_pid, 219 | version, 220 | ) 221 | 222 | if args.lsp: 223 | stdin, stdout = _binaryStdio() 224 | server = lsp.HdlCheckerLanguageServer() 225 | lsp.setupLanguageServerFeatures(server) 226 | server.start_io(stdin=stdin, stdout=stdout) 227 | else: 228 | if args.attach_to_pid is not None: 229 | _attachPids(args.attach_to_pid, os.getpid()) 230 | 231 | handlers.app.run(host=args.host, port=args.port, threads=10, server="waitress") 232 | 233 | 234 | def main(): 235 | return run(parseArguments()) 236 | 237 | 238 | if __name__ == "__main__": 239 | main() 240 | -------------------------------------------------------------------------------- /hdl_checker/static_check.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "VHDL static checking to find unused signals, ports and constants." 18 | 19 | import logging 20 | import re 21 | from typing import List, Tuple 22 | 23 | # from hdl_checker.path import Path 24 | from hdl_checker.diagnostics import ( 25 | DiagType, 26 | LibraryShouldBeOmited, 27 | ObjectIsNeverUsed, 28 | StaticCheckerDiag, 29 | ) 30 | 31 | _logger = logging.getLogger(__name__) 32 | 33 | _GET_SCOPE = re.compile( 34 | "|".join( 35 | [ 36 | r"^\s*entity\s+(?P\w+)\s+is\b", 37 | r"^\s*architecture\s+(?P\w+)\s+of\s+(?P\w+)", 38 | r"^\s*package\s+(?P\w+)\s+is\b", 39 | r"^\s*package\s+body\s+(?P\w+)\s+is\b", 40 | ] 41 | ), 42 | flags=re.I, 43 | ).finditer 44 | 45 | _NO_SCOPE_OBJECTS = re.compile( 46 | "|".join( 47 | [ 48 | r"^\s*library\s+(?P[\w\s,]+)", 49 | r"^\s*attribute\s+(?P[\w\s,]+)\s*:", 50 | ] 51 | ), 52 | flags=re.I, 53 | ) 54 | 55 | _ENTITY_OBJECTS = re.compile( 56 | "|".join( 57 | [ 58 | r"^\s*(?P[\w\s,]+)\s*:\s*(in|out|inout|buffer|linkage)\s+\w+", 59 | r"^\s*(?P[\w\s,]+)\s*:\s*\w+", 60 | ] 61 | ), 62 | flags=re.I, 63 | ).finditer 64 | 65 | _ARCH_OBJECTS = re.compile( 66 | "|".join( 67 | [ 68 | r"^\s*constant\s+(?P[\w\s,]+)\s*:", 69 | r"^\s*signal\s+(?P[\w,\s]+)\s*:", 70 | r"^\s*type\s+(?P\w+)\s*:", 71 | r"^\s*shared\s+variable\s+(?P[\w\s,]+)\s*:", 72 | ] 73 | ), 74 | flags=re.I, 75 | ).finditer 76 | 77 | _SHOULD_END_SCAN = re.compile( 78 | "|".join( 79 | [ 80 | r"\bgeneric\s+map", 81 | r"\bport\s+map", 82 | r"\bgenerate\b", 83 | r"\w+\s*:\s*entity", 84 | r"\bprocess\b", 85 | ] 86 | ) 87 | ).search 88 | 89 | 90 | def _getAreaFromMatch(dict_): # pylint: disable=inconsistent-return-statements 91 | """ 92 | Returns code area based on the match dict 93 | """ 94 | if dict_["entity_name"] is not None: 95 | return "entity" 96 | if dict_["architecture_name"] is not None: 97 | return "architecture" 98 | if dict_["package_name"] is not None: 99 | return "package" 100 | if dict_["package_body_name"] is not None: 101 | return "package_body" 102 | 103 | assert False, "Can't determine area from {}".format(dict_) # pragma: no cover 104 | 105 | 106 | def _getObjectsFromText(lines): 107 | """ 108 | Converts the iterator from _findObjects into a dict, whose key is the 109 | object's name and the value if the object's info 110 | """ 111 | objects = {} 112 | for name, info in _findObjects(lines): 113 | if name not in objects: 114 | objects[name] = info 115 | 116 | return objects 117 | 118 | 119 | def _findObjects(lines): 120 | """ 121 | Returns an iterator with the object name and a dict with info about its 122 | location 123 | """ 124 | lnum = 0 125 | area = None 126 | for _line in lines: 127 | line = re.sub(r"\s*--.*", "", _line) 128 | for match in _GET_SCOPE(line): 129 | area = _getAreaFromMatch(match.groupdict()) 130 | 131 | matches = [] 132 | if area is None: 133 | matches += _NO_SCOPE_OBJECTS.finditer(line) 134 | elif area == "entity": 135 | matches += _ENTITY_OBJECTS(line) 136 | elif area == "architecture": 137 | matches += _ARCH_OBJECTS(line) 138 | 139 | for match in matches: 140 | for key, value in match.groupdict().items(): 141 | if value is None: 142 | continue 143 | _group_d = match.groupdict() 144 | index = match.lastindex 145 | if "port" in _group_d.keys() and _group_d["port"] is not None: 146 | index -= 1 147 | start = match.start(index) 148 | end = match.end(index) 149 | 150 | # More than 1 declaration can be done in a single line. 151 | # Must strip white spaces and commas properly 152 | for submatch in re.finditer(r"(\w+)", value): 153 | # Need to decrement the last index because we have a group that 154 | # catches the port type (in, out, inout, etc) 155 | name = submatch.group(submatch.lastindex) 156 | yield name, { 157 | "lnum": lnum, 158 | "start": start + submatch.start(submatch.lastindex), 159 | "end": end + submatch.start(submatch.lastindex), 160 | "type": key, 161 | } 162 | lnum += 1 163 | if _SHOULD_END_SCAN(line): 164 | break 165 | 166 | 167 | def _getUnusedObjects(lines, objects): 168 | """Generator that yields objects that are only found once at the 169 | given buffer and thus are considered unused (i.e., we only found 170 | its declaration""" 171 | 172 | text = "" 173 | for line in lines: 174 | text += re.sub(r"\s*--.*", "", line) + " " 175 | 176 | for _object in objects: 177 | r_len = 0 178 | single = True 179 | for _ in re.finditer(r"\b%s\b" % _object, text, flags=re.I): 180 | r_len += 1 181 | if r_len > 1: 182 | single = False 183 | break 184 | if single: 185 | yield _object 186 | 187 | 188 | __COMMENT_TAG_SCANNER__ = re.compile( 189 | "|".join([r"\s*--\s*(?PTODO|FIXME|XXX)\s*:\s*(?P.*)"]) 190 | ) 191 | 192 | 193 | def _getCommentTags(lines): 194 | """ 195 | Generates diags from 'TODO', 'FIXME' and 'XXX' tags 196 | """ 197 | result = [] 198 | lnum = 0 199 | for line in lines: 200 | lnum += 1 201 | line_lc = line.lower() 202 | skip_line = True 203 | for tag in ("todo", "fixme", "xxx"): 204 | if tag in line_lc: 205 | skip_line = False 206 | break 207 | if skip_line: 208 | continue 209 | 210 | for match in __COMMENT_TAG_SCANNER__.finditer(line): 211 | _dict = match.groupdict() 212 | result += [ 213 | StaticCheckerDiag( 214 | line_number=lnum - 1, 215 | column_number=match.start(match.lastindex - 1), 216 | severity=DiagType.STYLE_INFO, 217 | text="%s: %s" % (_dict["tag"].upper(), _dict["text"]), 218 | ) 219 | ] 220 | return result 221 | 222 | 223 | def _getMiscChecks(objects): 224 | """ 225 | Get generic code hints (or it should do that sometime in the future...) 226 | """ 227 | if "library" not in [x["type"] for x in objects.values()]: 228 | return 229 | 230 | for library, obj in objects.items(): 231 | if obj["type"] != "library": 232 | continue 233 | if library == "work": 234 | yield LibraryShouldBeOmited( 235 | line_number=obj["lnum"], column_number=obj["start"], library=library 236 | ) 237 | 238 | 239 | def getStaticMessages(lines): 240 | # type: (Tuple[str, ...]) -> List[StaticCheckerDiag] 241 | "VHDL static checking" 242 | objects = _getObjectsFromText(lines) 243 | 244 | result = [] # type: List[StaticCheckerDiag] 245 | 246 | for _object in _getUnusedObjects(lines, objects.keys()): 247 | obj_dict = objects[_object] 248 | result += [ 249 | ObjectIsNeverUsed( 250 | line_number=obj_dict["lnum"], 251 | column_number=obj_dict["start"], 252 | object_type=obj_dict["type"], 253 | object_name=_object, 254 | ) 255 | ] 256 | 257 | return result + _getCommentTags(lines) + list(_getMiscChecks(objects)) 258 | 259 | 260 | def standalone(): # pragma: no cover 261 | """ 262 | Standalone entry point 263 | """ 264 | import sys 265 | 266 | logging.basicConfig(stream=sys.stdout, level=logging.INFO) 267 | for arg in sys.argv[1:]: 268 | print(arg) 269 | lines = [x.decode(errors="ignore") for x in open(arg, mode="rb").readlines()] 270 | for message in getStaticMessages(lines): 271 | print(message) 272 | print("=" * 10) 273 | 274 | 275 | if __name__ == "__main__": 276 | standalone() 277 | -------------------------------------------------------------------------------- /hdl_checker/tests/test_builder_utils.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | 18 | # pylint: disable=function-redefined 19 | # pylint: disable=invalid-name 20 | # pylint: disable=missing-docstring 21 | # pylint: disable=protected-access 22 | # pylint: disable=useless-object-inheritance 23 | 24 | import logging 25 | import os.path as p 26 | 27 | from mock import MagicMock, patch 28 | 29 | from hdl_checker.tests import TestCase, disableVunit, getTestTempPath 30 | 31 | from hdl_checker.builder_utils import ( 32 | foundVunit, 33 | getBuilderByName, 34 | getPreferredBuilder, 35 | getVunitSources, 36 | ) 37 | from hdl_checker.builders.fallback import Fallback 38 | from hdl_checker.builders.ghdl import GHDL 39 | from hdl_checker.builders.msim import MSim 40 | from hdl_checker.builders.xvhdl import XVHDL 41 | from hdl_checker.path import Path 42 | from hdl_checker.types import FileType 43 | 44 | _logger = logging.getLogger(__name__) 45 | 46 | TEST_TEMP_PATH = getTestTempPath(__name__) 47 | 48 | 49 | def _path(*args): 50 | # type: (str) -> str 51 | "Helper to reduce foorprint of p.join(TEST_TEMP_PATH, *args)" 52 | return p.join(TEST_TEMP_PATH, *args) 53 | 54 | 55 | class TestBuilderUtils(TestCase): 56 | def test_getBuilderByName(self): 57 | self.assertEqual(getBuilderByName("msim"), MSim) 58 | self.assertEqual(getBuilderByName("xvhdl"), XVHDL) 59 | self.assertEqual(getBuilderByName("ghdl"), GHDL) 60 | self.assertEqual(getBuilderByName("other"), Fallback) 61 | 62 | def test_getWorkingBuilders(self): 63 | # Test no working builders 64 | _logger.info("Checking no builder works") 65 | self.assertEqual(getPreferredBuilder(), Fallback) 66 | 67 | # Patch one builder 68 | with patch.object(MSim, "isAvailable", staticmethod(lambda: True)): 69 | self.assertEqual(getPreferredBuilder(), MSim) 70 | # Patch another builder with lower priority 71 | with patch.object(GHDL, "isAvailable", staticmethod(lambda: True)): 72 | self.assertEqual(getPreferredBuilder(), MSim) 73 | 74 | # Patch one builder 75 | with patch.object(GHDL, "isAvailable", staticmethod(lambda: True)): 76 | self.assertEqual(getPreferredBuilder(), GHDL) 77 | 78 | # Patch another builder 79 | with patch.object(MSim, "isAvailable", staticmethod(lambda: True)): 80 | self.assertEqual(getPreferredBuilder(), MSim) 81 | 82 | 83 | class Library(object): # pylint: disable=too-few-public-methods 84 | def __init__(self, name): 85 | self._name = name 86 | 87 | @property 88 | def name(self): 89 | return self._name 90 | 91 | 92 | class SourceFile(object): 93 | def __init__(self, name, library, vhdl_standard="2008"): 94 | self._name = name 95 | self._library = Library(library) 96 | self._vhdl_standard = vhdl_standard 97 | 98 | @property 99 | def name(self): 100 | return self._name 101 | 102 | @property 103 | def library(self): 104 | return self._library 105 | 106 | @property 107 | def vhdl_standard(self): 108 | return self._vhdl_standard 109 | 110 | 111 | class TestGetVunitSources(TestCase): 112 | def test_VunitNotFound(self): 113 | builder = MagicMock() 114 | with disableVunit: 115 | self.assertFalse(list(getVunitSources(builder))) 116 | 117 | @patch("vunit.VUnit.get_source_files") 118 | def test_VhdlBuilder(self, get_source_files): 119 | get_source_files.side_effect = [ 120 | [ 121 | SourceFile(name=_path("path_0.vhd"), library="libary_0"), 122 | SourceFile(name=_path("path_1.vhd"), library="libary_1"), 123 | ] 124 | ] 125 | 126 | builder = MagicMock() 127 | builder.builder_name = "msim" 128 | builder.file_types = {FileType.vhdl} 129 | 130 | self.maxDiff = None # pylint: disable=attribute-defined-outside-init 131 | self.assertTrue(foundVunit(), "Need VUnit for this test") 132 | # Should only have VHDL files 133 | sources = list(getVunitSources(builder)) 134 | 135 | get_source_files.assert_called_once() 136 | 137 | self.assertCountEqual( 138 | sources, 139 | { 140 | (Path(_path("path_0.vhd")), "libary_0", ("-2008",)), 141 | (Path(_path("path_1.vhd")), "libary_1", ("-2008",)), 142 | }, 143 | ) 144 | 145 | @patch("hdl_checker.builder_utils.findRtlSourcesByPath") 146 | @patch("vunit.verilog.VUnit.get_source_files") 147 | def test_SystemverilogOnlyBuilder(self, get_source_files, find_rtl_sources): 148 | get_source_files.side_effect = [ 149 | [ 150 | SourceFile(name=_path("path_0.vhd"), library="libary_0"), 151 | SourceFile(name=_path("path_1.vhd"), library="libary_1"), 152 | ] 153 | ] 154 | 155 | find_rtl_sources.return_value = [ 156 | Path(_path("some_header.vh")), 157 | Path(_path("some_header.svh")), 158 | ] 159 | 160 | builder = MagicMock() 161 | builder.builder_name = "msim" 162 | builder.file_types = {FileType.systemverilog} 163 | 164 | self.assertTrue(foundVunit(), "Need VUnit for this test") 165 | # Should only have VHDL files 166 | sources = list(getVunitSources(builder)) 167 | 168 | get_source_files.assert_called_once() 169 | find_rtl_sources.assert_called_once() 170 | 171 | self.assertCountEqual( 172 | sources, 173 | { 174 | (Path(_path("path_0.vhd")), "libary_0", ("-2008",)), 175 | (Path(_path("path_1.vhd")), "libary_1", ("-2008",)), 176 | (Path(_path("some_header.vh")), None, ()), 177 | (Path(_path("some_header.svh")), None, ()), 178 | }, 179 | ) 180 | 181 | @patch("hdl_checker.builder_utils._getSourcesFromVUnitModule") 182 | def test_VerilogOnlyBuilder(self, meth): 183 | builder = MagicMock() 184 | builder.builder_name = "msim" 185 | builder.file_types = {FileType.verilog} 186 | 187 | self.assertTrue(foundVunit(), "Need VUnit for this test") 188 | self.assertFalse(list(getVunitSources(builder))) 189 | meth.assert_not_called() 190 | 191 | @patch("hdl_checker.builder_utils.findRtlSourcesByPath") 192 | @patch("vunit.VUnit.get_source_files") 193 | @patch("vunit.verilog.VUnit.get_source_files") 194 | def test_VhdlAndSystemverilogOnlyBuilder( 195 | self, vhdl_method, sv_method, find_rtl_sources 196 | ): 197 | vhdl_method.side_effect = [ 198 | [ 199 | SourceFile(name=_path("path_0.vhd"), library="libary_0"), 200 | SourceFile(name=_path("path_1.vhd"), library="libary_1"), 201 | ] 202 | ] 203 | 204 | sv_method.side_effect = [ 205 | [ 206 | SourceFile(name=_path("path_2.sv"), library="libary_2"), 207 | SourceFile(name=_path("path_3.sv"), library="libary_3"), 208 | ] 209 | ] 210 | 211 | find_rtl_sources.return_value = [ 212 | Path(_path("some_header.vh")), 213 | Path(_path("some_header.svh")), 214 | ] 215 | 216 | builder = MagicMock() 217 | builder.builder_name = "xvhdl" 218 | builder.file_types = {FileType.vhdl, FileType.systemverilog} 219 | 220 | self.assertTrue(foundVunit(), "Need VUnit for this test") 221 | # Should only have VHDL files 222 | sources = list(getVunitSources(builder)) 223 | 224 | vhdl_method.assert_called_once() 225 | sv_method.assert_called_once() 226 | 227 | self.assertCountEqual( 228 | sources, 229 | { 230 | (Path(_path("path_0.vhd")), "libary_0", ()), 231 | (Path(_path("path_1.vhd")), "libary_1", ()), 232 | (Path(_path("path_2.sv")), "libary_2", ()), 233 | (Path(_path("path_3.sv")), "libary_3", ()), 234 | (Path(_path("some_header.vh")), None, ()), 235 | (Path(_path("some_header.svh")), None, ()), 236 | }, 237 | ) 238 | -------------------------------------------------------------------------------- /hdl_checker/tests/test_config_generator.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | 18 | # pylint: disable=function-redefined 19 | # pylint: disable=missing-docstring 20 | # pylint: disable=protected-access 21 | # pylint: disable=invalid-name 22 | 23 | import logging 24 | import os 25 | import os.path as p 26 | from tempfile import mkdtemp 27 | 28 | from mock import patch 29 | from webtest import TestApp # type: ignore # pylint:disable=import-error 30 | 31 | from hdl_checker.tests import TestCase 32 | 33 | import hdl_checker.handlers as handlers 34 | from hdl_checker.config_generators.simple_finder import SimpleFinder 35 | from hdl_checker.utils import removeDirIfExists 36 | 37 | _logger = logging.getLogger(__name__) 38 | 39 | 40 | class TestConfigGenerator(TestCase): 41 | maxDiff = None 42 | 43 | def setUp(self): 44 | self.app = TestApp(handlers.app) 45 | 46 | self.dummy_test_path = mkdtemp(prefix=__name__ + "_") 47 | 48 | os.mkdir(p.join(self.dummy_test_path, "path_a")) 49 | os.mkdir(p.join(self.dummy_test_path, "path_b")) 50 | os.mkdir(p.join(self.dummy_test_path, "v_includes")) 51 | os.mkdir(p.join(self.dummy_test_path, "sv_includes")) 52 | # Create empty sources and some extra files as well 53 | for path in ( 54 | "README.txt", # This shouldn't be included 55 | "nonreadable.txt", # This shouldn't be included 56 | p.join("path_a", "some_source.vhd"), 57 | p.join("path_a", "header_out_of_place.vh"), 58 | p.join("path_a", "source_tb.vhd"), 59 | p.join("path_b", "some_source.vhd"), 60 | p.join("path_b", "a_verilog_source.v"), 61 | p.join("path_b", "a_systemverilog_source.sv"), 62 | # Create headers for both extensions 63 | p.join("v_includes", "verilog_header.vh"), 64 | p.join("sv_includes", "systemverilog_header.svh"), 65 | # Make the tree 'dirty' with other source types 66 | p.join("path_a", "not_hdl_source.log"), 67 | p.join("path_a", "not_hdl_source.py"), 68 | ): 69 | _logger.info("Writing to %s", path) 70 | open(p.join(self.dummy_test_path, path), "w").write("") 71 | 72 | def teardown(self): 73 | # Create a dummy arrangement of sources 74 | removeDirIfExists(self.dummy_test_path) 75 | 76 | @patch( 77 | "hdl_checker.parser_utils.isFileReadable", 78 | lambda path: "nonreadable" not in path, 79 | ) 80 | def test_run_simple_config_gen(self): 81 | # type: (...) -> None 82 | finder = SimpleFinder([self.dummy_test_path]) 83 | 84 | config = finder.generate() 85 | 86 | self.assertCountEqual( 87 | config.pop("sources"), 88 | { 89 | p.join(self.dummy_test_path, "path_a", "some_source.vhd"), 90 | p.join(self.dummy_test_path, "path_b", "a_systemverilog_source.sv"), 91 | p.join(self.dummy_test_path, "path_a", "source_tb.vhd"), 92 | p.join(self.dummy_test_path, "path_b", "some_source.vhd"), 93 | p.join(self.dummy_test_path, "path_b", "a_verilog_source.v"), 94 | p.join(self.dummy_test_path, "sv_includes", "systemverilog_header.svh"), 95 | p.join(self.dummy_test_path, "v_includes", "verilog_header.vh"), 96 | p.join(self.dummy_test_path, "path_a", "header_out_of_place.vh"), 97 | }, 98 | ) 99 | 100 | # Assert there's no extra elements 101 | self.assertFalse(config) 102 | -------------------------------------------------------------------------------- /hdl_checker/tests/test_lsp_utils.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | """ 18 | Test code inside hdl_checker.lsp that doesn't depend on a server/client setup 19 | """ 20 | 21 | import logging 22 | import time 23 | from multiprocessing import Queue 24 | from tempfile import mkdtemp 25 | from typing import Any 26 | 27 | import mock 28 | import parameterized # type: ignore 29 | import unittest2 # type: ignore 30 | from pygls.types import DiagnosticSeverity, MessageType, Position, Range 31 | 32 | from hdl_checker import lsp 33 | from hdl_checker.diagnostics import CheckerDiagnostic, DiagType 34 | from hdl_checker.path import Path, TemporaryPath 35 | from hdl_checker.utils import debounce 36 | 37 | _logger = logging.getLogger(__name__) 38 | 39 | 40 | class TestCheckerDiagToLspDict(unittest2.TestCase): 41 | """ 42 | Test code inside hdl_checker.lsp that doesn't depend on a server/client 43 | setup 44 | """ 45 | 46 | @parameterized.parameterized.expand( 47 | [ 48 | (DiagType.INFO, DiagnosticSeverity.Information), 49 | (DiagType.STYLE_INFO, DiagnosticSeverity.Information), 50 | (DiagType.STYLE_WARNING, DiagnosticSeverity.Information), 51 | (DiagType.STYLE_ERROR, DiagnosticSeverity.Information), 52 | (DiagType.WARNING, DiagnosticSeverity.Warning), 53 | (DiagType.ERROR, DiagnosticSeverity.Error), 54 | (DiagType.NONE, DiagnosticSeverity.Error), 55 | ] 56 | ) 57 | def test_convertingDiagnosticType(self, diag_type, severity): 58 | # type: (...) -> Any 59 | """ 60 | Test conversion between hdl_checker.DiagType to pygls.types.DiagnosticSeverity 61 | """ 62 | _logger.info("Running %s and %s", diag_type, severity) 63 | 64 | diag = lsp.checkerDiagToLspDict( 65 | CheckerDiagnostic( 66 | checker="hdl_checker test", 67 | text="some diag", 68 | filename=Path("filename"), 69 | line_number=0, 70 | column_number=0, 71 | error_code="error code", 72 | severity=diag_type, 73 | ) 74 | ) 75 | 76 | self.assertEqual(diag.code, "error code") 77 | self.assertEqual(diag.source, "hdl_checker test") 78 | self.assertEqual(diag.message, "some diag") 79 | self.assertEqual(diag.severity, severity) 80 | self.assertEqual( 81 | diag.range, 82 | Range( 83 | start=Position(line=0, character=0), end=Position(line=0, character=1), 84 | ), 85 | ) 86 | 87 | def test_workspaceNotify(self) -> None: # pylint: disable=no-self-use 88 | """ 89 | Test server notification messages call the appropriate LS methods 90 | """ 91 | workspace = mock.Mock() 92 | workspace.show_message = mock.Mock() 93 | 94 | server = lsp.Server( 95 | workspace, root_dir=TemporaryPath(mkdtemp(prefix="hdl_checker_")) 96 | ) 97 | workspace.show_message.reset_mock() 98 | 99 | server._handleUiInfo("some info") # pylint: disable=protected-access 100 | workspace.show_message.assert_called_once_with("some info", MessageType.Info) 101 | workspace.show_message.reset_mock() 102 | 103 | server._handleUiWarning("some warning") # pylint: disable=protected-access 104 | workspace.show_message.assert_called_once_with( 105 | "some warning", MessageType.Warning 106 | ) 107 | workspace.show_message.reset_mock() 108 | 109 | server._handleUiError("some error") # pylint: disable=protected-access 110 | workspace.show_message.assert_called_once_with("some error", MessageType.Error) 111 | 112 | def test_debounceWithoutKey(self): 113 | _logger.info("#" * 100) 114 | interval = 0.1 115 | 116 | queue = Queue() 117 | 118 | def func(arg): 119 | _logger.info("Called with %s", arg) 120 | queue.put(arg) 121 | 122 | wrapped = debounce(interval)(func) 123 | self.assertTrue(queue.empty()) 124 | 125 | wrapped(1) 126 | wrapped(2) 127 | self.assertTrue(queue.empty()) 128 | 129 | time.sleep(2 * interval) 130 | 131 | self.assertEqual(queue.get(1), 2) 132 | self.assertTrue(queue.empty()) 133 | 134 | def test_debounceWithKey(self): 135 | _logger.info("#" * 100) 136 | interval = 0.1 137 | 138 | obj = mock.Mock() 139 | 140 | def func(arg): 141 | _logger.info("Called with %s", arg) 142 | obj(arg) 143 | 144 | wrapped = debounce(interval, "arg")(func) 145 | obj.assert_not_called() 146 | 147 | wrapped(1) 148 | wrapped(2) 149 | wrapped(3) 150 | 151 | obj.assert_not_called() 152 | 153 | time.sleep(2 * interval) 154 | 155 | obj.assert_has_calls( 156 | [mock.call(1), mock.call(2), mock.call(3),], any_order=True 157 | ) 158 | 159 | wrapped(4) 160 | wrapped(4) 161 | wrapped(4) 162 | time.sleep(2 * interval) 163 | 164 | obj.assert_has_calls( 165 | [mock.call(1), mock.call(2), mock.call(3), mock.call(4),], any_order=True 166 | ) 167 | -------------------------------------------------------------------------------- /hdl_checker/tests/test_misc.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "Misc tests of files, such as licensing and copyright" 18 | 19 | # pylint: disable=function-redefined 20 | # pylint: disable=missing-docstring 21 | # pylint: disable=protected-access 22 | # pylint: disable=invalid-name 23 | 24 | import logging 25 | import os.path as p 26 | import re 27 | import subprocess as subp 28 | 29 | import parameterized # type: ignore 30 | import unittest2 # type: ignore 31 | from mock import MagicMock, Mock, patch 32 | 33 | from hdl_checker.tests import linuxOnly 34 | 35 | from hdl_checker.builder_utils import BuilderName, getBuilderByName 36 | from hdl_checker.builders.fallback import Fallback 37 | from hdl_checker.builders.ghdl import GHDL 38 | from hdl_checker.builders.msim import MSim 39 | from hdl_checker.builders.xvhdl import XVHDL 40 | from hdl_checker.utils import _getLatestReleaseVersion, onNewReleaseFound, readFile 41 | 42 | _logger = logging.getLogger(__name__) 43 | 44 | _HEADER = re.compile( 45 | r"(?:--|#) This file is part of HDL Checker\.\n" 46 | r"(?:--|#)\n" 47 | r"(?:--|#) Copyright \(c\) 2015 - 2019 suoto \(Andre Souto\)\n" 48 | r"(?:--|#)\n" 49 | r"(?:--|#) HDL Checker is free software: you can redistribute it and/or modify\n" 50 | r"(?:--|#) it under the terms of the GNU General Public License as published by\n" 51 | r"(?:--|#) the Free Software Foundation, either version 3 of the License, or\n" 52 | r"(?:--|#) \(at your option\) any later version\.\n" 53 | r"(?:--|#)\n" 54 | r"(?:--|#) HDL Checker is distributed in the hope that it will be useful,\n" 55 | r"(?:--|#) but WITHOUT ANY WARRANTY; without even the implied warranty of\n" 56 | r"(?:--|#) MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE\. See the\n" 57 | r"(?:--|#) GNU General Public License for more details\.\n" 58 | r"(?:--|#)\n" 59 | r"(?:--|#) You should have received a copy of the GNU General Public License\n" 60 | r"(?:--|#) along with HDL Checker\. If not, see \.\n" 61 | ) 62 | 63 | 64 | def _getFiles(): 65 | for filename in subp.check_output( 66 | ["git", "ls-tree", "--name-only", "-r", "HEAD"] 67 | ).splitlines(): 68 | yield p.abspath(filename).decode() 69 | 70 | 71 | def _getRelevantFiles(): 72 | def _fileFilter(path): 73 | # Exclude versioneer files 74 | if p.basename(path) in ("_version.py", "versioneer.py"): 75 | return False 76 | if p.join(".ci", "test_support") in path: 77 | return False 78 | return path.split(".")[-1] in ("py", "sh", "ps1") 79 | 80 | return filter(_fileFilter, _getFiles()) 81 | 82 | 83 | def checkFile(filename): 84 | lines = readFile(filename) 85 | 86 | match = _HEADER.search(lines) 87 | return match is not None 88 | 89 | 90 | class TestFileHeaders(unittest2.TestCase): 91 | @parameterized.parameterized.expand([(x,) for x in _getRelevantFiles()]) 92 | def test_has_license(self, path): 93 | self.assertTrue(checkFile(path)) 94 | 95 | 96 | class TestBuilderUtils(unittest2.TestCase): 97 | def test_getBuilderByName(self): 98 | self.assertEqual(getBuilderByName(BuilderName.msim.value), MSim) 99 | self.assertEqual(getBuilderByName(BuilderName.ghdl.value), GHDL) 100 | self.assertEqual(getBuilderByName(BuilderName.xvhdl.value), XVHDL) 101 | self.assertEqual(getBuilderByName("foo"), Fallback) 102 | 103 | 104 | class TestReportingRelease(unittest2.TestCase): 105 | @patch("hdl_checker.utils.subp.Popen") 106 | def test_GetCorrectVersion(self, popen): 107 | process_mock = Mock() 108 | stdout = b"""\ 109 | 7e5264355da66f71e8d1b80887ac6df55b9829ef refs/tags/v0.6.2 110 | 7e5264355da66f71e8d1b80887ac6df55b9829ef refs/tags/v0.6.10 111 | a65602477ef860b48bacfa90a96d8518eb51f030 refs/tags/v0.6.3""" 112 | 113 | stderr = "" 114 | 115 | attrs = {"communicate.return_value": (stdout, stderr)} 116 | process_mock.configure_mock(**attrs) 117 | popen.return_value = process_mock 118 | 119 | self.assertEqual(_getLatestReleaseVersion(), (0, 6, 10)) 120 | 121 | @patch("hdl_checker.utils.subp.Popen") 122 | def test_TagExtractionFails(self, popen): 123 | # Tests if the function doesn't throw any exceptions in case the 124 | # reported format is different than what we expected 125 | process_mock = Mock() 126 | stdout = b"""\ 127 | refs/tags/v0.6.2 128 | refs/tags/v0.6.10 129 | refs/tags/v0.6.3""" 130 | 131 | stderr = "" 132 | 133 | attrs = {"communicate.return_value": (stdout, stderr)} 134 | process_mock.configure_mock(**attrs) 135 | popen.return_value = process_mock 136 | 137 | self.assertFalse(_getLatestReleaseVersion()) 138 | 139 | @patch("hdl_checker.utils.REPO_URL", "localhost") 140 | def test_HandlesNoConnection(self, *_): 141 | self.assertIsNone(_getLatestReleaseVersion()) 142 | 143 | @linuxOnly 144 | def test_UnmockedCallWorks(self): 145 | self.assertIsNotNone(_getLatestReleaseVersion()) 146 | 147 | 148 | @patch("hdl_checker.utils._getLatestReleaseVersion", return_value=(1, 0, 0)) 149 | @patch("hdl_checker.__version__", "0.9.0") 150 | def test_ReportIfCurrentIsOlder(*_): 151 | func = MagicMock() 152 | onNewReleaseFound(func) 153 | func.assert_called_once_with( 154 | "HDL Checker version 1.0.0 is out! (current version is 0.9.0)" 155 | ) 156 | 157 | 158 | @patch("hdl_checker.utils._getLatestReleaseVersion", return_value=(1, 0, 10)) 159 | @patch("hdl_checker.__version__", "1.0.9") 160 | def test_InterpretAsNumbersNotString(*_): 161 | func = MagicMock() 162 | onNewReleaseFound(func) 163 | func.assert_called_once_with( 164 | "HDL Checker version 1.0.10 is out! (current version is 1.0.9)" 165 | ) 166 | 167 | 168 | @patch("hdl_checker.utils._getLatestReleaseVersion", return_value=(1, 0, 0)) 169 | @patch("hdl_checker.__version__", "1.0.0") 170 | def test_DontReportIfCurrentIsUpToDate(*_): 171 | func = MagicMock() 172 | onNewReleaseFound(func) 173 | func.assert_not_called() 174 | 175 | 176 | @patch("hdl_checker.utils._getLatestReleaseVersion", return_value=(1, 0, 0)) 177 | @patch("hdl_checker.__version__", "1.0.1") 178 | def test_DontReportIfCurrentIsNewer(*_): 179 | func = MagicMock() 180 | onNewReleaseFound(func) 181 | func.assert_not_called() 182 | 183 | 184 | @patch("hdl_checker.utils._getLatestReleaseVersion", return_value=None) 185 | @patch("hdl_checker.__version__", "1.0.1") 186 | def test_DontReportIfFailedToGetVersion(*_): 187 | func = MagicMock() 188 | onNewReleaseFound(func) 189 | func.assert_not_called() 190 | 191 | 192 | @patch("hdl_checker.utils._getLatestReleaseVersion", return_value=None) 193 | @patch("hdl_checker.__version__", "0+unknown") 194 | def test_DontReportOnInvalidFormats(*_): 195 | func = MagicMock() 196 | onNewReleaseFound(func) 197 | func.assert_not_called() 198 | -------------------------------------------------------------------------------- /hdl_checker/tests/test_static_check.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | 18 | # pylint: disable=function-redefined, missing-docstring, protected-access 19 | 20 | import logging 21 | import re 22 | 23 | from nose2.tools import such # type: ignore 24 | from nose2.tools.params import params # type: ignore 25 | 26 | import hdl_checker.static_check as static_check 27 | from hdl_checker.diagnostics import DiagType, LibraryShouldBeOmited, StaticCheckerDiag 28 | 29 | _logger = logging.getLogger(__name__) 30 | 31 | with such.A("hdl_checker project") as it: 32 | 33 | @it.should("not repeat an object in the results") 34 | def test(): 35 | text = ["", "library ieee;", "", "library ieee;", ""] 36 | 37 | it.assertDictEqual( 38 | {"ieee": {"end": 12, "lnum": 1, "start": 8, "type": "library"}}, 39 | static_check._getObjectsFromText(text), 40 | ) 41 | 42 | @it.should("not scan after specific end of scan delimiters") # type: ignore 43 | @params( 44 | [" u0 : some_unit", " generic map ("], 45 | [" u0 : some_unit", " port map ("], 46 | [" u0 : entity work.some_unit"], 47 | [" p0 : process"], 48 | [" process(clk)"], 49 | ) 50 | def test(case, parm): 51 | _logger.info("Running test case '%s'", case) 52 | text = ["library foo;"] + parm + ["library bar;"] 53 | 54 | it.assertDictEqual( 55 | {"foo": {"end": 11, "lnum": 0, "start": 8, "type": "library"}}, 56 | static_check._getObjectsFromText(text), 57 | ) 58 | 59 | @it.should("extract comment tags") # type: ignore 60 | @params( 61 | " -- XXX: some warning", 62 | " -- TODO: something to do", 63 | " -- FIXME: something to fix", 64 | ) 65 | def test(case, parm): 66 | _logger.info("Running test case '%s'", case) 67 | expected = re.sub(r"\s*--\s*", "", parm) 68 | 69 | text = [ 70 | "library ieee;", 71 | " use ieee.std_logic_1164.all;", 72 | " use ieee.numeric_std.all;", 73 | "library basic_library;", 74 | "entity foo is", 75 | "", 76 | parm, 77 | "", 78 | " generic (", 79 | " DIVIDER_A : integer := 10;", 80 | " DIVIDER_B : integer := 20", 81 | " );", 82 | " port (", 83 | " clk_in_a : in std_logic;", 84 | " clk_out_a : out std_logic;", 85 | "", 86 | " clk_in_b : in std_logic;", 87 | " clk_out_b : out std_logic", 88 | "", 89 | " );", 90 | "end foo;", 91 | "", 92 | "architecture foo of foo is", 93 | "begin", 94 | "clk_out_a <= not clk_in_a;", 95 | "-- clk_out_b <= not clk_in_b;", 96 | "end architecture foo;", 97 | ] 98 | 99 | it.assertCountEqual( 100 | [ 101 | StaticCheckerDiag( 102 | line_number=6, 103 | column_number=4, 104 | severity=DiagType.STYLE_INFO, 105 | text=expected, 106 | ) 107 | ], 108 | static_check._getCommentTags(text), 109 | ) 110 | 111 | @it.should("get misc checks") # type: ignore 112 | def test(): 113 | text = [ 114 | "entity foo is", 115 | " port (", 116 | " clk_in_a : in std_logic;", 117 | " clk_out_a : out std_logic;", 118 | "", 119 | " clk_in_b : in std_logic;", 120 | " clk_out_b : out std_logic", 121 | "", 122 | " );", 123 | "end foo;", 124 | ] 125 | 126 | objects = static_check._getObjectsFromText(text) 127 | 128 | it.assertCountEqual([], static_check._getMiscChecks(objects)) 129 | 130 | with it.having("an entity-architecture pair"): 131 | 132 | @it.has_setup 133 | def setup(): 134 | it.text = [ 135 | "library ieee;", 136 | " use ieee.std_logic_1164.all;", 137 | " use ieee.numeric_std.all;", 138 | "library work;", 139 | " use work.some_package.all;", 140 | "library basic_library;", 141 | "entity foo is", 142 | " generic (", 143 | " DIVIDER_A : integer := 10;", 144 | " DIVIDER_B : integer := 20", 145 | " );", 146 | " port (", 147 | " clk_in_a : in std_logic;", 148 | " clk_out_a : out std_logic;", 149 | "", 150 | " clk_in_b : in std_logic;", 151 | " clk_out_b : out std_logic", 152 | "", 153 | " );", 154 | "end foo;", 155 | "", 156 | "architecture foo of foo is", 157 | "begin", 158 | "clk_out_a <= not clk_in_a;", 159 | "-- clk_out_b <= not clk_in_b;", 160 | "end architecture foo;", 161 | ] 162 | 163 | @it.should("get VHDL objects from an entity-architecture pair") # type: ignore 164 | def test(): 165 | it.assertDictEqual( 166 | { 167 | "DIVIDER_A": {"end": 18, "lnum": 8, "start": 8, "type": "generic"}, 168 | "DIVIDER_B": {"end": 18, "lnum": 9, "start": 8, "type": "generic"}, 169 | "basic_library": { 170 | "end": 21, 171 | "lnum": 5, 172 | "start": 8, 173 | "type": "library", 174 | }, 175 | "clk_in_a": {"end": 17, "lnum": 12, "start": 8, "type": "port"}, 176 | "clk_in_b": {"end": 17, "lnum": 15, "start": 8, "type": "port"}, 177 | "clk_out_a": {"end": 18, "lnum": 13, "start": 8, "type": "port"}, 178 | "clk_out_b": {"end": 18, "lnum": 16, "start": 8, "type": "port"}, 179 | "ieee": {"end": 12, "lnum": 0, "start": 8, "type": "library"}, 180 | "work": {"end": 12, "lnum": 3, "start": 8, "type": "library"}, 181 | }, 182 | static_check._getObjectsFromText(it.text), 183 | ) 184 | 185 | @it.should("get misc checks") # type: ignore 186 | def test(): 187 | objects = static_check._getObjectsFromText(it.text) 188 | 189 | it.assertCountEqual( 190 | [LibraryShouldBeOmited(line_number=3, column_number=8, library="work")], 191 | static_check._getMiscChecks(objects), 192 | ) 193 | 194 | @it.should("get unused VHDL objects") # type: ignore 195 | def test(): 196 | objects = static_check._getObjectsFromText(it.text) 197 | it.assertCountEqual( 198 | ["basic_library", "DIVIDER_A", "DIVIDER_B", "clk_in_b", "clk_out_b"], 199 | static_check._getUnusedObjects(it.text, objects), 200 | ) 201 | 202 | with it.having("a package-package body pair"): 203 | 204 | @it.has_setup 205 | def setup(): 206 | it.text = [ 207 | "library ieee;", 208 | "package very_common_pkg is", 209 | ' constant VIM_HDL_VERSION : string := "0.1";', 210 | "end package;", 211 | "package body very_common_pkg is", 212 | "end package body;", 213 | ] 214 | 215 | @it.should("get VHDL objects from a package-package body pair") # type: ignore 216 | def test(): 217 | 218 | it.assertDictEqual( 219 | {"ieee": {"end": 12, "lnum": 0, "start": 8, "type": "library"}}, 220 | static_check._getObjectsFromText(it.text), 221 | ) 222 | 223 | @it.should("get unused VHDL objects") # type: ignore 224 | def test(): 225 | objects = static_check._getObjectsFromText(it.text) 226 | it.assertCountEqual( 227 | ["ieee"], static_check._getUnusedObjects(it.text, objects) 228 | ) 229 | 230 | 231 | it.createTests(globals()) 232 | -------------------------------------------------------------------------------- /hdl_checker/tests/test_verilog_parser.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | 18 | # pylint: disable=function-redefined, missing-docstring, protected-access 19 | 20 | import json 21 | import logging 22 | from tempfile import NamedTemporaryFile 23 | 24 | from parameterized import parameterized_class # type: ignore 25 | 26 | from hdl_checker.tests import TestCase, writeListToFile 27 | 28 | from hdl_checker.parsers.elements.dependency_spec import ( 29 | RequiredDesignUnit, 30 | IncludedPath, 31 | ) 32 | from hdl_checker.parsers.elements.identifier import VerilogIdentifier 33 | from hdl_checker.parsers.elements.parsed_element import Location 34 | from hdl_checker.parsers.verilog_parser import VerilogDesignUnit, VerilogParser 35 | from hdl_checker.path import Path 36 | from hdl_checker.serialization import StateEncoder, jsonObjectHook 37 | from hdl_checker.types import DesignUnitType 38 | 39 | _logger = logging.getLogger(__name__) 40 | 41 | 42 | def parametrizeClassWithFileTypes(cls): 43 | keys = ["filetype"] 44 | values = [(x,) for x in ("v", "vh", "sv", "svh")] 45 | 46 | return parameterized_class(keys, values)(cls) 47 | 48 | 49 | @parametrizeClassWithFileTypes 50 | class TestVerilogSource(TestCase): 51 | maxDiff = None 52 | 53 | @classmethod 54 | def setUpClass(cls): 55 | cls.filename = NamedTemporaryFile(suffix="." + cls.filetype).name 56 | 57 | writeListToFile( 58 | cls.filename, 59 | [ 60 | '`include "some/include"', 61 | "", 62 | "import some_package::*;", 63 | "import another_package :: some_name ;", 64 | "", 65 | "module clock_divider", 66 | " #(parameter DIVISION = 5)", 67 | " (// Usual ports", 68 | " input clk,", 69 | " input rst,", 70 | " // Output clock divided", 71 | " output clk_div);", 72 | " localparam foo::bar = std::randomize(cycles);", 73 | "endmodule", 74 | "", 75 | "package \\m$gPkg! ;", 76 | " integer errCnt = 0;", 77 | " integer warnCnt = 0;", 78 | "endpackage", 79 | "", 80 | ], 81 | ) 82 | 83 | cls.source = VerilogParser(Path(cls.filename)) 84 | 85 | def test_GetDesignUnits(self): 86 | design_units = list(self.source.getDesignUnits()) 87 | _logger.debug("Design units: %s", design_units) 88 | self.assertCountEqual( 89 | design_units, 90 | [ 91 | VerilogDesignUnit( 92 | owner=self.source.filename, 93 | name="clock_divider", 94 | type_=DesignUnitType.entity, 95 | locations={(5, 7)}, 96 | ), 97 | VerilogDesignUnit( 98 | owner=self.source.filename, 99 | name="\\m$gPkg!", 100 | type_=DesignUnitType.package, 101 | locations={(15, 8)}, 102 | ), 103 | ], 104 | ) 105 | 106 | def test_GetDependencies(self): 107 | expected = [ 108 | IncludedPath( 109 | owner=Path(self.filename), 110 | name=VerilogIdentifier("some/include"), 111 | locations=(Location(line=0, column=8),), 112 | ) 113 | ] 114 | 115 | if self.filetype in ("sv", "svh"): 116 | expected += [ 117 | RequiredDesignUnit( 118 | owner=Path(self.filename), 119 | name=VerilogIdentifier("some_package"), 120 | library=None, 121 | locations=(Location(line=2, column=7),), 122 | ), 123 | RequiredDesignUnit( 124 | owner=Path(self.filename), 125 | name=VerilogIdentifier("another_package"), 126 | library=None, 127 | locations=(Location(line=3, column=8),), 128 | ), 129 | RequiredDesignUnit( 130 | owner=Path(self.filename), 131 | name=VerilogIdentifier("foo"), 132 | library=None, 133 | locations=(Location(line=12, column=14),), 134 | ), 135 | ] 136 | 137 | self.assertCountEqual(self.source.getDependencies(), expected) 138 | 139 | def test_CacheRecovery(self): 140 | state = json.dumps(self.source, cls=StateEncoder) 141 | _logger.info("State before: %s", state) 142 | recovered = json.loads(state, object_hook=jsonObjectHook) 143 | self.assertEqual(self.source.filename, recovered.filename) 144 | -------------------------------------------------------------------------------- /hdl_checker/types.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "Common type definitions for type hinting" 18 | from collections import namedtuple 19 | from enum import Enum 20 | from typing import NamedTuple, Optional, Tuple, Union 21 | 22 | from hdl_checker.exceptions import UnknownTypeExtension 23 | from hdl_checker.parsers.elements.identifier import Identifier 24 | from hdl_checker.path import Path 25 | 26 | 27 | class DesignUnitType(str, Enum): 28 | "Specifies tracked design unit types" 29 | package = "package" 30 | entity = "entity" 31 | context = "context" 32 | 33 | 34 | BuildFlags = Tuple[str, ...] 35 | LibraryAndUnit = namedtuple("LibraryAndUnit", ["library", "unit"]) 36 | 37 | RebuildUnit = NamedTuple( 38 | "RebuildUnit", (("name", Identifier), ("type_", DesignUnitType)) 39 | ) 40 | RebuildLibraryUnit = NamedTuple( 41 | "RebuildLibraryUnit", (("name", Identifier), ("library", Identifier)) 42 | ) 43 | RebuildPath = NamedTuple("RebuildPath", (("path", Path),)) 44 | 45 | RebuildInfo = Union[RebuildUnit, RebuildLibraryUnit, RebuildPath] 46 | 47 | 48 | class FileType(Enum): 49 | "RTL file types" 50 | vhdl = "vhdl" 51 | verilog = "verilog" 52 | systemverilog = "systemverilog" 53 | 54 | @staticmethod 55 | def fromPath(path): 56 | # type: (Path) -> FileType 57 | "Extracts FileType from the given path's extension" 58 | ext = path.name.split(".")[-1].lower() 59 | if ext in ("vhd", "vhdl"): 60 | return FileType.vhdl 61 | if ext in ("v", "vh"): 62 | return FileType.verilog 63 | if ext in ("sv", "svh"): 64 | return FileType.systemverilog 65 | raise UnknownTypeExtension(path) 66 | 67 | def __jsonEncode__(self): 68 | """ 69 | Gets a dict that describes the current state of this object 70 | """ 71 | return {"value": self.name} 72 | 73 | @classmethod 74 | def __jsonDecode__(cls, state): 75 | """Returns an object of cls based on a given state""" 76 | return cls(state["value"]) 77 | 78 | 79 | class BuildFlagScope(Enum): 80 | """ 81 | Scopes of a given set of flags. Values of the items control the actual 82 | fields extracted from the JSON config 83 | """ 84 | 85 | source_specific = "source_specific" 86 | single = "single" 87 | dependencies = "dependencies" 88 | all = "global" 89 | 90 | 91 | class MarkupKind(Enum): 92 | "LSP Markup kinds" 93 | PlainText = "plaintext" 94 | Markdown = "markdown" 95 | 96 | 97 | # A location on a source file 98 | Location = NamedTuple("Location", (("line", Optional[int]), ("column", Optional[int]))) 99 | 100 | # A location range within a source file 101 | Range = NamedTuple("Range", (("start", Location), ("end", Optional[Location]))) 102 | 103 | 104 | class ConfigFileOrigin(str, Enum): 105 | "Specifies tracked design unit types" 106 | user = "user" 107 | generated = "generated" 108 | -------------------------------------------------------------------------------- /run_tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # This file is part of HDL Checker. 3 | # 4 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 5 | # 6 | # HDL Checker is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # HDL Checker is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with HDL Checker. If not, see . 18 | 19 | set -e 20 | 21 | PATH_TO_THIS_SCRIPT=$(readlink -f "$(dirname "$0")") 22 | 23 | TOX_ARGS="$*" 24 | 25 | # Need to add some variables so that uploading coverage from witihin the 26 | # container to codecov works 27 | docker run \ 28 | --rm \ 29 | --mount type=bind,source="$PATH_TO_THIS_SCRIPT",target=/hdl_checker \ 30 | --env USER_ID="$(id -u)" \ 31 | --env GROUP_ID="$(id -g)" \ 32 | --env TOX_ARGS="$TOX_ARGS" \ 33 | --env USERNAME="$USER" \ 34 | suoto/hdl_checker_test:latest /bin/bash -c '.ci/scripts/docker_entry_point.sh' 35 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [versioneer] 2 | VCS = git 3 | style = pep440 4 | versionfile_source = hdl_checker/_version.py 5 | tag_prefix = v 6 | [bdist_wheel] 7 | universal = 1 8 | 9 | [mypy] 10 | python_version = 3.7 11 | follow_imports = silent 12 | warn_unused_configs = true 13 | warn_unused_ignores = true 14 | 15 | [isort] 16 | forced_separate=nose2,hdl_checker.tests,hdl_checker 17 | multi_line_output=3 18 | include_trailing_comma=True 19 | force_grid_wrap=0 20 | use_parentheses=True 21 | line_length=88 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # This file is part of HDL Checker. 2 | # 3 | # Copyright (c) 2015 - 2019 suoto (Andre Souto) 4 | # 5 | # HDL Checker is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # HDL Checker is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with HDL Checker. If not, see . 17 | "HDL Checker installation script" 18 | 19 | import setuptools # type: ignore 20 | import versioneer 21 | 22 | LONG_DESCRIPTION = open("README.md", "rb").read().decode(encoding='utf8', errors='replace') 23 | 24 | CLASSIFIERS = """\ 25 | Development Status :: 5 - Production/Stable 26 | Environment :: Console 27 | Intended Audience :: Developers 28 | License :: OSI Approved :: GNU General Public License v3 (GPLv3) 29 | Operating System :: Microsoft :: Windows 30 | Operating System :: POSIX :: Linux 31 | Programming Language :: Python 32 | Programming Language :: Python :: 3 33 | Programming Language :: Python :: 3.6 34 | Programming Language :: Python :: 3.7 35 | Programming Language :: Python :: 3.8 36 | Topic :: Software Development 37 | Topic :: Scientific/Engineering :: Electronic Design Automation (EDA) 38 | Topic :: Text Editors :: Integrated Development Environments (IDE) 39 | """ 40 | 41 | setuptools.setup( 42 | name = 'hdl_checker', 43 | version = versioneer.get_version(), 44 | description = 'HDL code checker', 45 | long_description = LONG_DESCRIPTION, 46 | long_description_content_type = "text/markdown", 47 | author = 'Andre Souto', 48 | author_email = 'andre820@gmail.com', 49 | url = 'https://github.com/suoto/hdl_checker', 50 | license = 'GPLv3', 51 | keywords = 'VHDL Verilog SystemVerilog linter LSP language server protocol vimhdl vim-hdl', 52 | platforms = 'any', 53 | packages = setuptools.find_packages(), 54 | install_requires = ['argcomplete', 55 | 'argparse', 56 | 'backports.functools_lru_cache; python_version<"3.2"', 57 | 'bottle>=0.12.9', 58 | 'enum34>=1.1.6; python_version<"3.3"', 59 | 'future>=0.14.0', 60 | 'futures; python_version<"3.2"', 61 | 'prettytable>=0.7.2', 62 | 'pygls==0.9.1', 63 | 'requests>=2.20.0', 64 | 'six>=1.10.0', 65 | 'tabulate>=0.8.5', 66 | 'typing>=3.7.4', 67 | 'waitress>=0.9.0', ], 68 | cmdclass = versioneer.get_cmdclass(), 69 | entry_points = { 70 | 'console_scripts' : ['hdl_checker=hdl_checker.server:main', ] 71 | }, 72 | classifiers=CLASSIFIERS.splitlines(), 73 | ) 74 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = {py36,py37,py38}-{linux,windows} 3 | 4 | [testenv] 5 | platform = 6 | linux: linux 7 | windows: win32|win64 8 | 9 | setenv = 10 | linux: GHDL_PATH = {env:GHDL_PATH:{env:HOME}/builders/ghdl/bin/} 11 | linux: MODELSIM_PATH = {env:MODELSIM_PATH:{env:HOME}/builders/msim/modelsim_ase/linuxaloem/} 12 | linux: XSIM_PATH = {env:XSIM_PATH:/xsim/bin/} 13 | linux: CI_TEST_SUPPORT_PATH = ./.ci/test_support/ 14 | 15 | windows: GHDL_PATH = 16 | windows: MODELSIM_PATH = 17 | windows: XSIM_PATH = 18 | windows: CI_TEST_SUPPORT_PATH = .\\.ci\\test_support\\ 19 | 20 | # install pytest in the virtualenv where commands will be executed 21 | deps = 22 | coverage==4.1 23 | mock==2.0.0 24 | nose2-cov==1.0a4 25 | nose2==0.9.1 26 | parameterized==0.7.0 27 | rainbow_logging_handler==2.2.2 28 | six==1.10.0 29 | testfixtures==4.10.0 30 | unittest2==1.1.0 31 | vunit-hdl==4.0.8 32 | WebTest==2.0.23 33 | 34 | 35 | commands = 36 | python .ci/scripts/run_tests.py --log-level DEBUG [] 37 | 38 | # Winows runs outside of docker, need to do this so coverage data is sent to codecov 39 | windows: coverage combine 40 | windows: coverage xml 41 | windows: coverage report 42 | windows: coverage html 43 | -------------------------------------------------------------------------------- /unittest.cfg: -------------------------------------------------------------------------------- 1 | [unittest] 2 | plugins = nose2.plugins.layers 3 | # [coverage] 4 | # always-on = True 5 | # coverage = vimhdl 6 | # coverage-report = html 7 | [layer-reporter] 8 | always-on = True 9 | colors = True 10 | highlight-words = A 11 | having 12 | should 13 | indent = 14 | 15 | [log-capture] 16 | always-on = False 17 | clear-handlers = True # Comment this to get full logging 18 | format = "%%(levelname)-7s | %%(asctime)s | %%(name)s @ %%(funcName)s():%%(lineno)d %%(threadName)s | %%(message)s" 19 | 20 | date-format = "%%H:%%M:%%S" 21 | log-level = DEBUG 22 | --------------------------------------------------------------------------------