├── .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 |
--------------------------------------------------------------------------------