From f2506479af3e8eac34b675582878ac783a5effda Mon Sep 17 00:00:00 2001 From: Nikolay Govorov Date: Fri, 24 Jul 2026 02:32:25 +0100 Subject: Linter for licenses headers --- README.md | 3 ++- REUSE.toml | 2 +- tasks/licenses | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6e464e1..ba734bc 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,8 @@ repository cannot grant itself an exemption. ## Licensing policy `tasks/licenses` runs a pinned REUSE version and verifies the repository's -licensing metadata. In Rust projects it also runs a pinned `cargo deny check`. +licensing metadata and canonical SPDX copyright headers. In Rust projects it +also runs a pinned `cargo deny check`. ## License diff --git a/REUSE.toml b/REUSE.toml index c87ec2f..515ecc4 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -7,5 +7,5 @@ version = 1 path = [ "README.md", ] -SPDX-FileCopyrightText = "2026 Nikolay Govorov " +SPDX-FileCopyrightText = "2026 Nikolay Govorov" SPDX-License-Identifier = "CC-BY-4.0" diff --git a/tasks/licenses b/tasks/licenses index 538826f..cb8a932 100755 --- a/tasks/licenses +++ b/tasks/licenses @@ -6,6 +6,45 @@ set -eu +check_copyright_headers() { + invalid_headers=$( + git grep -n -I -E \ + 'Copyright[[:space:]]+(\([cC]\)|©)|SPDX-FileCopyrightText:' \ + -- . \ + ':(exclude)*.md' \ + ':(exclude)LICENSE' \ + ':(exclude)LICENSES/**' \ + ':(exclude)COPYING*' | + awk 'match($0, /:[0-9]+:/) { + line = substr($0, RSTART + 1, RLENGTH - 2) + if (line > 10) { + next + } + + text = substr($0, RSTART + RLENGTH) + reason = "" + if (text ~ /^[[:space:]]*(())?$/) { + reason = "expected 2026 Nikolay Govorov" + } + + if (reason != "") { + print reason ": " $0 + } + }' + ) + + if [ -n "$invalid_headers" ]; then + printf '%s\n%s\n' 'Invalid copyright headers:' "$invalid_headers" >&2 + return 1 + fi +} + +check_copyright_headers reuse lint if [ -f Cargo.toml ]; then -- Gilti