blob: 4719cd7a74cdbdd9e5b71643b0e772f7d70f9870 [file] [log] [blame]
# Copyright 2017-2020 The Verible Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Build rules for running the Verilog style linter (and other checks)
Usage:
load(
"//verilog/tools:verilog_style_lint.bzl",
"verilog_style_lint",
)
# Scan this package and subpackages for Verilog sources and analyze them.
verilog_style_lint.package_test(
name = "default_style_lint_test",
subpackages = ["sub/dir1", "sub/dir2", ... ],
)
or if you want to manually list files to scan:
verilog_style_lint.test(
name = "style_lint_test",
srcs = [targets...],
)
"""
_syntax_tool = "//verilog/tools/syntax:verible-verilog-syntax"
_linter_tool = "//verilog/tools/lint:verible-verilog-lint"
_verilog_extensions = [
".v",
".sv",
".vh",
".svh",
]
_verilog_srcs_glob_patterns = ["**/*" + ext for ext in _verilog_extensions]
def has_verilog_extension(filename):
for ext in _verilog_extensions:
if filename.endswith(ext):
return True
return False
_style_lint_fail_message = ("Expected clean report but found style diagnostics." +
"")
_syntax_test_attrs = {
"expect_fail": attr.bool(),
"srcs": attr.label_list(
allow_files = True,
mandatory = True,
),
"_compiler": attr.label(
allow_files = True,
default = Label(_syntax_tool),
executable = True,
cfg = "host",
),
}
def _syntax_test_impl(ctx):
"""Checks inputs files for valid Verilog syntax.
Automatically filters out and ignores non-verilog files.
Args:
ctx: Context of this rule invocation.
Returns:
runfiles information.
"""
verilog_srcs = [f for f in ctx.files.srcs if has_verilog_extension(f.path)]
args = [f.short_path for f in verilog_srcs]
base_command = [ctx.executable._compiler.short_path] + args
if not verilog_srcs:
native.warn("verilog_syntax.test did not find any Verilog sources.")
command = [] # no-op
elif ctx.attr.expect_fail:
command = ["!"] + base_command # '!' inverts exit status (shell)
else:
command = base_command
script = "#!/bin/bash\n" + " ".join(command) + "\n"
ctx.actions.write(
content = script,
output = ctx.outputs.executable,
is_executable = True,
)
exec_inputs = [ctx.executable._compiler] if verilog_srcs else []
runfiles = ctx.runfiles(files = exec_inputs + verilog_srcs)
return [DefaultInfo(runfiles = runfiles)]
_syntax_test = rule(
attrs = _syntax_test_attrs,
executable = True,
test = True,
implementation = _syntax_test_impl,
)
def _verilog_style_lint_report(name, srcs, flags = None):
"""Rule for producing a lint report on a collection of sources.
Args:
name: label of lint report.
srcs: list of source files to scan (can use native.glob()).
flags: list of flags for running the verilog_lint binary.
Examples: --parse_fatal, --lint_fatal.
"""
output = name + "-style_lint_report.txt"
if srcs:
files_target = name + "_files"
native.filegroup(
name = files_target,
srcs = srcs,
)
lint_cmd = "$(location {tool}) {flags} $(SRCS) > $@".format(
tool = _linter_tool,
flags = " ".join(flags or []),
)
native.genrule(
name = name,
tools = [_linter_tool],
srcs = [files_target],
outs = [output],
cmd = lint_cmd,
)
else:
# No sources, don't bother running linter.
native.genrule(
name = name,
outs = [output],
cmd = "touch $@",
)
def _check_empty_script(filename, message, expect_fail):
"""Returns a script check that checks for empty diagnostics in a file.
Args:
filename: name of file containing diagnostic output.
message: message to print when there are unexpected findings.
message may be multiline.
expect_fail: if True expect there to be findings, and invert test status.
Returns:
A string that is a shell script for the diagnostic test.
"""
if expect_fail:
return """
test -s {} ||
{{ echo Expected diagnostics, but found none. ; exit 1 ;}}
""".format(filename)
else:
return """
test ! -s {filename} ||
{{ cat <<EOF
{message}
EOF
cat {filename} ;
exit 1 ;}}
""".format(filename = filename, message = message)
# Attributes for _test_diagnostics.
_test_diagnostics_attrs = {
# If true, invert the test's exit status, useful when expecting failures.
"expect_fail": attr.bool(),
# report refers to a single file that captures output from a linter.
# An empty report is treated as lint-clean (pass), a non-empty report
# is interpreted as a failure.
"report": attr.label(
mandatory = True,
allow_single_file = True,
),
}
def _style_lint_test_diagnostics_impl(ctx):
"""Checks a report for diagnostics. Fails if report contains any text."""
report = ctx.file.report
ctx.actions.write(
output = ctx.outputs.executable,
content = _check_empty_script(
expect_fail = ctx.attr.expect_fail,
filename = report.short_path,
message = _style_lint_fail_message,
),
)
runfiles = ctx.runfiles(files = [report])
return [DefaultInfo(runfiles = runfiles)]
_verilog_style_lint_diagnostics_test = rule(
attrs = _test_diagnostics_attrs,
test = True,
implementation = _style_lint_test_diagnostics_impl,
)
def _verilog_style_lint_test(name, srcs, flags = [], expect_fail = None):
"""Macro for running Verilog style lint tests in the silo.
Args:
name: name of test.
srcs: list of source files to scan (e.g. can use glob()).
flags: list of flags for running the verilog_lint binary.
Note: --norules_config_search is used for each test.
expect_fail: if True, expect to find errors (invert status).
"""
forced_flags = [
"--norules_config_search",
]
_verilog_style_lint_report(
name = name + "-report",
srcs = srcs,
flags = flags + forced_flags,
)
_verilog_style_lint_diagnostics_test(
name = name,
expect_fail = expect_fail,
report = name + "-report",
)
def _package_style_lint_test(
name,
subpackages = None,
exclude = None,
expect_fail = False):
"""Tests Verilog lint on sources inside the invoking directory.
Args:
name: string, name of test.
subpackages: paths to subpackages with lint test rules.
TODO(fangism): can this be automated?
exclude: list of files or patterns to exclude from checking.
expect_fail: if True, expect to find errors (invert status).
"""
_verilog_style_lint_test(
name = name + "-local",
srcs = native.glob(
# Recursively scan for files, stopping at package boundaries,
# i.e. directories with BUILD files.
include = _verilog_srcs_glob_patterns,
exclude = exclude or [],
),
expect_fail = expect_fail,
)
if subpackages:
native.test_suite(
name = name + "-sub",
tests = [
"//{0}/{1}:{2}".format(native.package_name(), sub, name)
for sub in subpackages
],
)
native.test_suite(
name = name,
tests = [name + "-local"] + ([name + "-sub"] if subpackages else []),
)
# Load these modules to access public functions as its members.
verilog_syntax = struct(
test = _syntax_test,
)
verilog_style_lint = struct(
package_test = _package_style_lint_test,
test = _verilog_style_lint_test,
)