1*bf6873c5SCy Schubert#!/usr/bin/perl 2*bf6873c5SCy Schubert# 3*bf6873c5SCy Schubert# Check source files for SPDX-License-Identifier fields. 4*bf6873c5SCy Schubert# 5*bf6873c5SCy Schubert# Examine all source files in a distribution to check that they contain an 6*bf6873c5SCy Schubert# SPDX-License-Identifier field. This does not check the syntax or whether 7*bf6873c5SCy Schubert# the identifiers are valid. 8*bf6873c5SCy Schubert# 9*bf6873c5SCy Schubert# The canonical version of this file is maintained in the rra-c-util package, 10*bf6873c5SCy Schubert# which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>. 11*bf6873c5SCy Schubert# 12*bf6873c5SCy Schubert# Copyright 2018-2021 Russ Allbery <eagle@eyrie.org> 13*bf6873c5SCy Schubert# 14*bf6873c5SCy Schubert# Permission is hereby granted, free of charge, to any person obtaining a 15*bf6873c5SCy Schubert# copy of this software and associated documentation files (the "Software"), 16*bf6873c5SCy Schubert# to deal in the Software without restriction, including without limitation 17*bf6873c5SCy Schubert# the rights to use, copy, modify, merge, publish, distribute, sublicense, 18*bf6873c5SCy Schubert# and/or sell copies of the Software, and to permit persons to whom the 19*bf6873c5SCy Schubert# Software is furnished to do so, subject to the following conditions: 20*bf6873c5SCy Schubert# 21*bf6873c5SCy Schubert# The above copyright notice and this permission notice shall be included in 22*bf6873c5SCy Schubert# all copies or substantial portions of the Software. 23*bf6873c5SCy Schubert# 24*bf6873c5SCy Schubert# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25*bf6873c5SCy Schubert# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26*bf6873c5SCy Schubert# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 27*bf6873c5SCy Schubert# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28*bf6873c5SCy Schubert# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 29*bf6873c5SCy Schubert# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 30*bf6873c5SCy Schubert# DEALINGS IN THE SOFTWARE. 31*bf6873c5SCy Schubert# 32*bf6873c5SCy Schubert# SPDX-License-Identifier: MIT 33*bf6873c5SCy Schubert 34*bf6873c5SCy Schubertuse 5.010; 35*bf6873c5SCy Schubertuse strict; 36*bf6873c5SCy Schubertuse warnings; 37*bf6873c5SCy Schubert 38*bf6873c5SCy Schubertuse lib "$ENV{C_TAP_SOURCE}/tap/perl"; 39*bf6873c5SCy Schubert 40*bf6873c5SCy Schubertuse Test::RRA qw(skip_unless_automated); 41*bf6873c5SCy Schubertuse Test::RRA::Automake qw(all_files automake_setup); 42*bf6873c5SCy Schubert 43*bf6873c5SCy Schubertuse File::Basename qw(basename); 44*bf6873c5SCy Schubertuse Test::More; 45*bf6873c5SCy Schubert 46*bf6873c5SCy Schubert# File name (the file without any directory component) and path patterns to 47*bf6873c5SCy Schubert# skip for this check. 48*bf6873c5SCy Schubert## no critic (RegularExpressions::ProhibitFixedStringMatches) 49*bf6873c5SCy Schubertmy @IGNORE = ( 50*bf6873c5SCy Schubert qr{ \A LICENSE \z }xms, # Generated file with no license itself 51*bf6873c5SCy Schubert qr{ \A (NEWS|THANKS|TODO) \z }xms, # Package license should be fine 52*bf6873c5SCy Schubert qr{ \A README ( [.] .* )? \z }xms, # Package license should be fine 53*bf6873c5SCy Schubert qr{ \A (Makefile|libtool) \z }xms, # Generated file 54*bf6873c5SCy Schubert qr{ ~ \z }xms, # Backup files 55*bf6873c5SCy Schubert qr{ [.] l?a \z }xms, # Created by libtool 56*bf6873c5SCy Schubert qr{ [.] o \z }xms, # Compiler objects 57*bf6873c5SCy Schubert qr{ [.] output \z }xms, # Test data 58*bf6873c5SCy Schubert); 59*bf6873c5SCy Schubertmy @IGNORE_PATHS = ( 60*bf6873c5SCy Schubert qr{ \A debian/ }xms, # Found in debian/* branches 61*bf6873c5SCy Schubert qr{ \A docs/metadata/ }xms, # Package license should be fine 62*bf6873c5SCy Schubert qr{ \A docs/protocol[.](html|txt) \z }xms, # Generated by xml2rfc 63*bf6873c5SCy Schubert qr{ \A m4/ (libtool|lt.*) [.] m4 \z }xms, # Files from Libtool 64*bf6873c5SCy Schubert qr{ \A perl/Build \z }xms, # Perl build files 65*bf6873c5SCy Schubert qr{ \A perl/MANIFEST \z }xms, # Perl build files 66*bf6873c5SCy Schubert qr{ \A perl/MYMETA [.] }xms, # Perl build files 67*bf6873c5SCy Schubert qr{ \A perl/blib/ }xms, # Perl build files 68*bf6873c5SCy Schubert qr{ \A perl/cover_db/ }xms, # Perl test files 69*bf6873c5SCy Schubert qr{ \A perl/_build }xms, # Perl build files 70*bf6873c5SCy Schubert qr{ \A php/Makefile [.] global \z }xms, # Created by phpize 71*bf6873c5SCy Schubert qr{ \A php/autom4te [.] cache/ }xms, # Created by phpize 72*bf6873c5SCy Schubert qr{ \A php/acinclude [.] m4 \z }xms, # Created by phpize 73*bf6873c5SCy Schubert qr{ \A php/build/ }xms, # Created by phpize 74*bf6873c5SCy Schubert qr{ \A php/config [.] (guess|sub) \z }xms, # Created by phpize 75*bf6873c5SCy Schubert qr{ \A php/configure [.] (ac|in) \z }xms, # Created by phpize 76*bf6873c5SCy Schubert qr{ \A php/ltmain [.] sh \z }xms, # Created by phpize 77*bf6873c5SCy Schubert qr{ \A php/run-tests [.] php \z }xms, # Created by phpize 78*bf6873c5SCy Schubert qr{ \A python/ .* [.] egg-info/ }xms, # Python build files 79*bf6873c5SCy Schubert qr{ \A tests/config/ (?!README) }xms, # Test configuration 80*bf6873c5SCy Schubert qr{ \A tests/tmp/ }xms, # Temporary test files 81*bf6873c5SCy Schubert); 82*bf6873c5SCy Schubert## use critic 83*bf6873c5SCy Schubert 84*bf6873c5SCy Schubert# Only run this test during automated testing, since failure doesn't indicate 85*bf6873c5SCy Schubert# any user-noticable flaw in the package itself. 86*bf6873c5SCy Schubertskip_unless_automated('SPDX identifier tests'); 87*bf6873c5SCy Schubert 88*bf6873c5SCy Schubert# Set up Automake testing. 89*bf6873c5SCy Schubertautomake_setup(); 90*bf6873c5SCy Schubert 91*bf6873c5SCy Schubert# Check a single file for an occurrence of the string. 92*bf6873c5SCy Schubert# 93*bf6873c5SCy Schubert# $path - Path to the file 94*bf6873c5SCy Schubert# 95*bf6873c5SCy Schubert# Returns: undef 96*bf6873c5SCy Schubertsub check_file { 97*bf6873c5SCy Schubert my ($path) = @_; 98*bf6873c5SCy Schubert my $filename = basename($path); 99*bf6873c5SCy Schubert 100*bf6873c5SCy Schubert # Ignore files in the whitelist and binary files. 101*bf6873c5SCy Schubert for my $pattern (@IGNORE) { 102*bf6873c5SCy Schubert return if $filename =~ $pattern; 103*bf6873c5SCy Schubert } 104*bf6873c5SCy Schubert for my $pattern (@IGNORE_PATHS) { 105*bf6873c5SCy Schubert return if $path =~ $pattern; 106*bf6873c5SCy Schubert } 107*bf6873c5SCy Schubert return if !-T $path; 108*bf6873c5SCy Schubert 109*bf6873c5SCy Schubert # Scan the file. 110*bf6873c5SCy Schubert my ($saw_legacy_notice, $saw_spdx, $skip_spdx); 111*bf6873c5SCy Schubert open(my $file, '<', $path) or BAIL_OUT("Cannot open $path: $!"); 112*bf6873c5SCy Schubert while (defined(my $line = <$file>)) { 113*bf6873c5SCy Schubert if ($line =~ m{ Generated [ ] by [ ] libtool [ ] }xms) { 114*bf6873c5SCy Schubert close($file) or BAIL_OUT("Cannot close $path: $!"); 115*bf6873c5SCy Schubert return; 116*bf6873c5SCy Schubert } 117*bf6873c5SCy Schubert if ($line =~ m{ \b See \s+ LICENSE \s+ for \s+ licensing }xms) { 118*bf6873c5SCy Schubert $saw_legacy_notice = 1; 119*bf6873c5SCy Schubert } 120*bf6873c5SCy Schubert if ($line =~ m{ \b SPDX-License-Identifier: \s+ \S+ }xms) { 121*bf6873c5SCy Schubert $saw_spdx = 1; 122*bf6873c5SCy Schubert last; 123*bf6873c5SCy Schubert } 124*bf6873c5SCy Schubert if ($line =~ m{ no \s SPDX-License-Identifier \s registered }xms) { 125*bf6873c5SCy Schubert $skip_spdx = 1; 126*bf6873c5SCy Schubert last; 127*bf6873c5SCy Schubert } 128*bf6873c5SCy Schubert } 129*bf6873c5SCy Schubert close($file) or BAIL_OUT("Cannot close $path: $!"); 130*bf6873c5SCy Schubert 131*bf6873c5SCy Schubert # If there is a legacy license notice, report a failure regardless of file 132*bf6873c5SCy Schubert # size. Otherwise, skip files under 1KB. They can be rolled up into the 133*bf6873c5SCy Schubert # overall project license and the license notice may be a substantial 134*bf6873c5SCy Schubert # portion of the file size. 135*bf6873c5SCy Schubert if ($saw_legacy_notice) { 136*bf6873c5SCy Schubert ok(!$saw_legacy_notice, "$path has legacy license notice"); 137*bf6873c5SCy Schubert } else { 138*bf6873c5SCy Schubert ok($saw_spdx || $skip_spdx || -s $path < 1024, $path); 139*bf6873c5SCy Schubert } 140*bf6873c5SCy Schubert return; 141*bf6873c5SCy Schubert} 142*bf6873c5SCy Schubert 143*bf6873c5SCy Schubert# Scan every file. We don't declare a plan since we skip a lot of files and 144*bf6873c5SCy Schubert# don't want to precalculate the file list. 145*bf6873c5SCy Schubertmy @paths = all_files(); 146*bf6873c5SCy Schubertfor my $path (@paths) { 147*bf6873c5SCy Schubert check_file($path); 148*bf6873c5SCy Schubert} 149*bf6873c5SCy Schubertdone_testing(); 150