Skip to content

contributing: Use SPDX copyright tags in file headers - #7743

Open
wenzeslaus wants to merge 1 commit into
OSGeo:mainfrom
wenzeslaus:spdx-headers
Open

contributing: Use SPDX copyright tags in file headers#7743
wenzeslaus wants to merge 1 commit into
OSGeo:mainfrom
wenzeslaus:spdx-headers

Conversation

@wenzeslaus

@wenzeslaus wenzeslaus commented Jul 17, 2026

Copy link
Copy Markdown
Member

Replaces the COPYRIGHT: field and the GPL paragraph with SPDX tags in the 984 files where the conversion is mechanical.

  • Years and names preserved as is (no fixes), but split into multiple lines.
  • Every entry has "Other GRASS authors" (even if it did not have one before) without a year range unless it is the only author in the original COPYRIGHT.
  • Leaves out all the unclear cases, fixes, e.g. misspellings and updates from AUTHORS and Git.

A typical replacements looks like this:

- * COPYRIGHT:    (C) 2008 by Glynn Clements and the GRASS Development Team
- *
- *               This program is free software under the GNU General Public
- *               License (>=v2). Read the file COPYING that comes with GRASS
- *               for details.
+ * SPDX-FileCopyrightText: 2008 Glynn Clements
+ * SPDX-FileCopyrightText: Other GRASS authors
+ * SPDX-License-Identifier: GPL-2.0-or-later

The policy decisions were made during GRASS Community Meeting 2026 in San Michele all'Adige.

Addresses partially #4190. Supersedes #7556, #7557, #7729 and #7716.

Details: rules, scope, deferred cases, and how it was checked (AI generated)

Rules

  • One holder per SPDX-FileCopyrightText line. Named holders first, the collective line last, the license identifier after all copyright lines.
  • Years are copied verbatim. Never normalized, collapsed, gap-filled, reordered, or corrected. v.net.connectivity keeps 2002-20014; g.gui.timeline keeps 2012-13. Those years are wrong, but they were already wrong, and fixing them is a separate question from converting the header.
  • The collective line carries years only where it is the sole copyright line. Where a holder is named, the named line carries the years and the collective line is bare.
  • Every converted file gets the collective line, including the 37 whose notice never named the team. Those notices name only an individual or an institution, for example lib/lidar/* (Politecnico di Milano), imagery/i.topo.corr/* (E. Jorge Tizado) and lib/psdriver/* (Glynn Clements), and they gain SPDX-FileCopyrightText: Other GRASS authors alongside the existing holder. This is deliberate rather than a side effect of the pattern matching: COPYING states that GRASS is copyright by members of the GRASS Development Team, so the collective already holds copyright in those files and the per-file notice was merely incomplete. The named holder is preserved exactly and loses nothing by it. It is the one place where a converted notice says something the old text did not, so it is called out here rather than left to be discovered file by file.
  • The notice text is the only input. Neither AUTHOR(S) nor the git history was consulted, so holder names are copied as written: v.report keeps MN, r.drain keeps metz, r.tileset keeps the misspelled Cedric Shoc. Normalizing them means reading AUTHOR(S) to decide a copyright holder, which is a follow-up with a person in the loop, not a regex.
  • MODULE:, AUTHOR(S): and PURPOSE: are untouched, as is every byte outside the header span.

Scope

converted 984
deferred, reported below 31
headers that define the convention, follow-up 5
archived email, never convertible 1
baseline git grep -lE 'COPYRIGHT:[[:space:]]*\([Cc]\)' 1021

Out of scope: roughly 1300 files whose notices use the bare (C) form with no COPYRIGHT: field, mostly lib/ Doxygen blocks and wxGUI docstrings. This PR converts the COPYRIGHT:-field corpus only, so the tree is left in a mixed state on purpose.

Depends on a follow-up

This PR puts Other GRASS authors in 984 files, and COPYING does not define that term yet. A follow-up adds the definition and handles the five files that specify the convention rather than merely carrying it: doc/development/style_guide.md, lib/gis/parser_script.c, general/g.parser/g.parser.md and .html, and utils/copywrite.pl. They are excluded here because each of them states what a header should look like, so they need reading rather than converting, and because two of them generate headers into new files.

RFC 2 says existing copyright headers must not be stripped without agreement. Renaming the collective in 984 files is a notational change rather than a change of holder only because the project writes down that the two names mean the same group — which is what the COPYING definition does. So this should not merge before that definition lands, even though it can be reviewed now.

Not converted

The 31 deferred files are attached as deferred-cases.md, quoted verbatim with the reason each was left alone. Summary:

  • 14 where the holder contains a comma, an ampersand, or the word and. Such punctuation separates two holders in Cedric Shoc, Martin Landa and sits inside one holder in mundialis GmbH & Co.KG and GDF Hannover bR, http://www.gdf-hannover.de. The notice alone cannot tell them apart.
  • 10 with more than one copyright clause under a single label, including the eight v.delaunay files carrying (C) RMIT 1993 alongside the GRASS notice.
  • 3 under the LGPL, not the GPL: i.albedo, i.aster.toar, i.evapo.time. Converting those to GPL-2.0-or-later would misstate the license.
  • 3 where a field interrupts the header, so no known license paragraph follows the notice.
  • 1 whose license paragraphs appear in a combination the tree does not otherwise use.

lib/vector/rtree/docs/MAILS is excluded outright: line 134 is a >-quoted 2001 email asking permission to add GRASS headers. It quotes a header rather than carrying one, and rewriting it would falsify a record.

How it was done, and how to check it

A deterministic script, not a language model. It is attached (spdx_headers.py.txt) together with its tests (spdx_headers_test.py.txt) and the validators (gates.py.txt), so the result can be reproduced or regenerated against a newer base. It is not committed, being a one-off: the repository already carries one abandoned header generator from 2006, utils/copywrite.pl, which has been unrunnable for years and is itself a conversion target.

The script keys on COPYRIGHT:\s*\([Cc]\), so a second run is a no-op by construction. It edits by byte-offset splice and never rebuilds a file from a line list, which is why no trailing newline can drift.

Validators, all run against the raw output before any formatter touched it:

  • Exactly the 984 intended files changed, no others.
  • No byte changed outside the header span; every deferred file is byte-identical.
  • Trailing bytes unchanged, and the size delta equals the span delta exactly.
  • No word appears in a new copyright line that was not in the old notice. You cannot invent a holder if you can only reuse words that were already there.
  • The four-digit year multiset is identical before and after.
  • One holder per line; no emitted C line exceeds 78 columns, below clang-format's limit of 80.
  • pre-commit run --all-files leaves the output byte-identical — 21 hooks, no reformatting.

29 tests cover the harness, including every comment syntax in the tree (*, #, ##, %, CMake #[[, undecorated), the verbatim-year table, and one case per deferral reason.

The conversion itself was performed by the script above. The script, the analysis behind it, and this description were written with AI assistance (Claude Code with Opus 4.8 and Fable 5).

deferred-cases.md
gates.py.txt
spdx_headers_test.py.txt
spdx_headers.py.txt

@wenzeslaus

wenzeslaus commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

AI-review as is (not counting prompting and adjustments) since this is difficult to review for humans (edit, it slipped without my prefix):

I checked the claims in the description against the diff itself, without using the conversion script, by parsing the patches this PR reports. Every added line matches one of two shapes, SPDX-FileCopyrightText: or SPDX-License-Identifier: GPL-2.0-or-later, and every removed line is a COPYRIGHT: field, a known GPL paragraph, or a blank comment line. No copyright line names more than one holder, no holder contains a word that was not already in the notice it replaced, no four-digit year is lost or altered, and no emitted C line passes 78 columns. Two things worth recording.

lib/psdriver/psdriver.ps is invisible in the diff view

GitHub reports the file as +0/-0 with no patch, having classified the PostScript as binary. There is no .gitattributes rule for .ps, so this is content heuristics. The change is real and correct, and fetching the blob shows the expected three lines:

%  SPDX-FileCopyrightText: 2007 Glynn Clements
%  SPDX-FileCopyrightText: Other GRASS authors
%  SPDX-License-Identifier: GPL-2.0-or-later

The practical consequence is that this file cannot be reviewed in the web UI, so it needs git show or a blob fetch to see at all.

It also explains the file count arithmetic, which otherwise looks like drift: the local diff is +2142/-5365, this PR reports +2139/-5354, and the gap is exactly 3 additions and 11 deletions, which is precisely psdriver.ps. Nothing is missing from the PR; GitHub is not counting a file it thinks is binary. Worth a line in the description so nobody has to work that out.

The validators are less independent than the count suggests

The description lists a set of checks, and they did all pass, but they are not all independent evidence. The validator script imports the converter and calls it to recompute each header span, so the span-integrity and token-conservation checks compare the converter against its own expectation. A systematically wrong span finder would produce a matching wrong expectation and pass both.

The checks that are genuinely independent are the ones that do not go through the converter:

  • pre-commit run --all-files leaving the output byte-identical, which is what would have caught the trailing-newline regression in docs: adding SPDX License IDs to the source code #DeepSeek4 #7729,
  • the assertion that the prose block was consumed whole rather than in part, which reads the original text after the span,
  • and the patch-level checks in the first paragraph above, which parse the diff and import nothing.

That is roughly three independent facts plus a set of consistency checks, which is enough for a mechanical conversion of this shape, but the list should not be read as a dozen separate confirmations. For a one-off script I would not restructure this; it is worth knowing when weighing how much the passing checks prove.

On sequencing

The description is right that this should not merge before COPYING defines "Other GRASS authors", and the reasoning under RFC 2 is the substantive part rather than a formality: the rename is a notational change rather than a change of holder precisely because the project writes down that the two names mean the same group. Nothing in the PR state enforces that ordering at the moment, so it rests on a reader reaching that paragraph. Whether that matters depends on how quickly the follow-up appears.

@github-actions github-actions Bot added windows Microsoft Windows specific GUI wxGUI related macOS macOS specific vector Related to vector data processing raster Related to raster data processing temporal Related to temporal data processing Python Related code is in Python C Related code is in C C++ Related code is in C++ HTML Related code is in HTML translation Message translation related database Related to database management libraries module docs general display imagery tests Related to Test Suite raster3d notebook misc CMake labels Jul 17, 2026
Replace the COPYRIGHT field and the GPL paragraph with SPDX tags in the
984 files where the conversion is mechanical. The notice text is the only
input: neither AUTHOR(S) nor the git history was consulted.

Years are copied verbatim, including where they are plainly wrong, such as
2002-20014 in v.net.connectivity. Correcting them is a separate question
from converting the header, and guessing at them is how the previous
attempt invented copyright holders.

The collective line carries years only where it is the sole copyright
line. Where a holder is named, the named line carries the years and the
collective line is bare. COPYING defines the collective.

31 files are left untouched and reported separately: notices naming
several holders where the punctuation is ambiguous, notices with more than
one copyright clause, three files under the LGPL, and two where a field
interrupts the header. lib/vector/rtree/docs/MAILS is excluded outright,
being an archived email that quotes a header rather than carrying one.

The headers that define the convention are converted in a separate pull
request, and the roughly 1300 files whose notices use the bare (C) form
without a COPYRIGHT field are out of scope here.

The conversion was performed by a deterministic script, not by a language
model. The script and its report are attached to the pull request. It was
written with AI assistance (Claude Opus 4.8); the decisions are the
author's.
@nilason

nilason commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

I really do not understand the incentive to change copyright holder in the proposed way (or any other for that matter), and then describe the change in another file (that it means the same thing as before!?). It looks particularly strange when "GRASS Development Team" is the only copyright holder:

- * COPYRIGHT:    (C) 2008 by the GRASS Development Team
- *
- *               This program is free software under the GNU General Public
- *               License (>=v2). Read the file COPYING that comes with GRASS
- *               for details.
+ * SPDX-FileCopyrightText: Other GRASS authors
+ * SPDX-License-Identifier: GPL-2.0-or-later

@wenzeslaus

Copy link
Copy Markdown
Member Author

The "collective author" makes more sense in the files with multiple authors which we may eventually get in the future, but I was purposefully not reading from the author field in this iteration. The old GRASS Development Team is not well defined and misleading, but some future effort will have to clean up that. (Our governance has Core Development Team which is a different entity.) The particular wording is not ideal, but I/we at the community meeting did not find anything better yet. (As for AI suggestions, Fable was suggesting to drop it.)

As for general need for this, @neteler is the one who initiated this.

@nilason

nilason commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

The "collective author" makes more sense in the files with multiple authors which we may eventually get in the future, but I was purposefully not reading from the author field in this iteration. The old GRASS Development Team is not well defined and misleading, but some future effort will have to clean up that. (Our governance has Core Development Team which is a different entity.) The particular wording is not ideal, but I/we at the community meeting did not find anything better yet. (As for AI suggestions, Fable was suggesting to drop it.)

As for general need for this, @neteler is the one who initiated this.

Let us bring this seemingly trivial issue to the grass dev discourse list, before merge. I’m not convinced.

@wenzeslaus

Copy link
Copy Markdown
Member Author

Sounds good to me, but I can lead only the implementation part at this point. No problem updating my PRs (but no promises on my response time right now).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

C Related code is in C C++ Related code is in C++ CMake database Related to database management display docs general GUI wxGUI related HTML Related code is in HTML imagery libraries macOS macOS specific misc module notebook Python Related code is in Python raster Related to raster data processing raster3d temporal Related to temporal data processing tests Related to Test Suite translation Message translation related vector Related to vector data processing windows Microsoft Windows specific

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants