From 66f14a16aa8c3ecd4df89206dfa54add7d45f3fd Mon Sep 17 00:00:00 2001 From: Sebastian Abrahamsson Date: Tue, 13 Oct 2026 19:03:50 +0200 Subject: [PATCH 1/2] Lint the Python code with ruff Check pyflakes and pycodestyle errors and warnings (E, F, W) with a line length of 120, like black. The rules are listed explicitly and CI pins the ruff version, so a new ruff release doesn't change what is checked. The code passes as it is. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/lint.yml | 21 +++++++++++++++++++++ pyproject.toml | 8 ++++++++ 2 files changed, 29 insertions(+) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..5d802f0 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,21 @@ +name: 'lint' + +# Lints the Python code with ruff, configured in pyproject.toml. + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + + ruff: + runs-on: ubuntu-24.04 + name: '🧹 ruff' + steps: + + - name: '🧰 Checkout' + uses: actions/checkout@v4 + + - name: '🧹 ruff check' + run: pipx run ruff==0.17.0 check --output-format=github diff --git a/pyproject.toml b/pyproject.toml index 9fa0513..5bf727d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,6 +59,14 @@ vunit_python_bridge = [ [tool.black] line-length = 120 +[tool.ruff] +line-length = 120 + +[tool.ruff.lint] +# pyflakes and pycodestyle errors and warnings. Listed explicitly so that a new ruff release +# doesn't change the rules. +select = ["E", "F", "W"] + [tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_*.py"] From 87d6124f22af925cfee009eb9a7a8cd646ebbf40 Mon Sep 17 00:00:00 2001 From: Sebastian Abrahamsson Date: Tue, 13 Oct 2026 19:06:50 +0200 Subject: [PATCH 2/2] Check the license headers of the source files tools/check_license_headers.py checks that every tracked Python, VHDL and C source file has the MPL 2.0 notice and a copyright line ending with the current year, and the lint workflow runs it after ruff. The three files of the VHPI application still said 2014-2023. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/lint.yml | 12 +++- .../native/vhpi/python_pkg.c | 2 +- .../native/vhpi/python_pkg.h | 2 +- .../native/vhpi/python_pkg_vhpi.c | 2 +- tools/check_license_headers.py | 58 +++++++++++++++++++ 5 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 tools/check_license_headers.py diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 5d802f0..fa2e4be 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,6 +1,7 @@ name: 'lint' -# Lints the Python code with ruff, configured in pyproject.toml. +# Lints the Python code with ruff, configured in pyproject.toml, and checks the license +# headers of the source files. on: push: @@ -9,9 +10,9 @@ on: jobs: - ruff: + lint: runs-on: ubuntu-24.04 - name: '🧹 ruff' + name: '🧹 ruff · license headers' steps: - name: '🧰 Checkout' @@ -19,3 +20,8 @@ jobs: - name: '🧹 ruff check' run: pipx run ruff==0.17.0 check --output-format=github + + # Also when ruff fails, so that one run reports both + - name: '📜 License headers' + if: ${{ !cancelled() }} + run: python3 tools/check_license_headers.py diff --git a/src/vunit_python_bridge/native/vhpi/python_pkg.c b/src/vunit_python_bridge/native/vhpi/python_pkg.c index 543a4e8..8fd2725 100644 --- a/src/vunit_python_bridge/native/vhpi/python_pkg.c +++ b/src/vunit_python_bridge/native/vhpi/python_pkg.c @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. // -// Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com +// Copyright (c) 2014-2026, Lars Asplund lars.anders.asplund@gmail.com #include "python_pkg.h" diff --git a/src/vunit_python_bridge/native/vhpi/python_pkg.h b/src/vunit_python_bridge/native/vhpi/python_pkg.h index 3c48b5d..298a4d4 100644 --- a/src/vunit_python_bridge/native/vhpi/python_pkg.h +++ b/src/vunit_python_bridge/native/vhpi/python_pkg.h @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. // -// Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com +// Copyright (c) 2014-2026, Lars Asplund lars.anders.asplund@gmail.com #include #include diff --git a/src/vunit_python_bridge/native/vhpi/python_pkg_vhpi.c b/src/vunit_python_bridge/native/vhpi/python_pkg_vhpi.c index fe6d226..7187081 100644 --- a/src/vunit_python_bridge/native/vhpi/python_pkg_vhpi.c +++ b/src/vunit_python_bridge/native/vhpi/python_pkg_vhpi.c @@ -4,7 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this file, // You can obtain one at http://mozilla.org/MPL/2.0/. // -// Copyright (c) 2014-2023, Lars Asplund lars.anders.asplund@gmail.com +// Copyright (c) 2014-2026, Lars Asplund lars.anders.asplund@gmail.com #include #include diff --git a/tools/check_license_headers.py b/tools/check_license_headers.py new file mode 100644 index 0000000..913f00b --- /dev/null +++ b/tools/check_license_headers.py @@ -0,0 +1,58 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. +# +# Copyright (c) 2014-2026, Lars Asplund lars.anders.asplund@gmail.com + +""" +Check that every Python, VHDL and C source file starts with the MPL 2.0 notice and a copyright +line ending with the current year. Lists the files that don't and exits with 1. +""" + +import datetime +import re +import subprocess +import sys +from pathlib import Path + +ROOT = Path(__file__).parent.parent +SUFFIXES = (".py", ".vhd", ".vhd.in", ".c", ".h") +# The header is within the first lines, after an optional description line +HEADER_LINES = 15 +NOTICE = [ + "This Source Code Form is subject to the terms of the Mozilla Public", + "License, v. 2.0. If a copy of the MPL was not distributed with this file,", + "You can obtain one at http://mozilla.org/MPL/2.0/.", +] +COPYRIGHT = re.compile(r"Copyright \(c\) 2014-(\d{4}), Lars Asplund lars\.anders\.asplund@gmail\.com") + + +def header_problem(text: str, year: int): + """ + What is wrong with the header of a file, or None. + """ + header = "\n".join(text.splitlines()[:HEADER_LINES]) + if not all(line in header for line in NOTICE): + return "no MPL 2.0 notice" + match = COPYRIGHT.search(header) + if match is None: + return "no copyright line 'Copyright (c) 2014-, Lars Asplund lars.anders.asplund@gmail.com'" + if int(match.group(1)) != year: + return f"copyright ends with {match.group(1)}, not {year}" + return None + + +def main() -> int: + files = subprocess.run(["git", "ls-files"], cwd=ROOT, capture_output=True, text=True, check=True).stdout.split() + year = datetime.date.today().year + problems = [] + for name in sorted(name for name in files if name.endswith(SUFFIXES)): + problem = header_problem((ROOT / name).read_text(encoding="utf-8"), year) + if problem: + problems.append(f"{name}: {problem}") + print("\n".join(problems) or "All license headers are in place.") + return 1 if problems else 0 + + +if __name__ == "__main__": + sys.exit(main())