2 # Copyright 2009 Ian Beckwith <ianb@erislabs.net>
3 # License: GPL v2 or later.
7 $me=($0=~/(?:.*\/)?(.*)/)[0];
11 use Digest::SHA qw(sha256_hex);
15 our $CLSCANDIR="debian/clscan";
16 our $FILESCACHE="$CLSCANDIR/files.yaml";
17 our $NEWFILES="$CLSCANDIR/new.txt";
18 our $COPYRIGHTSTUB="$CLSCANDIR/copyright.in";
20 my $gpl_boilerplate=<<"EOL";
21 This program is free software: you can redistribute it and/or modify
22 it under the terms of the GNU General Public License as published by
23 the Free Software Foundation; either version 3 of the License, or
24 (at your option) any later version.
26 This program is distributed in the hope that it will be useful,
27 but WITHOUT ANY WARRANTY; without even the implied warranty of
28 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 GNU General Public License for more details.
31 You should have received a copy of the GNU General Public License
32 along with this program. If not, see <http://www.gnu.org/licenses/>.
35 my $lgpl2_boilerplate=<<"EOL";
36 This program is free software; you can redistribute it and/or modify it
37 under the terms of the GNU Library General Public License as published
38 by the Free Software Foundation; either version 2, or (at your option)
41 This program is distributed in the hope that it will be useful,
42 but WITHOUT ANY WARRANTY; without even the implied warranty of
43 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44 Library General Public License for more details.
46 You should have received a copy of the GNU Library General Public
47 License along with this program; if not, write to the Free Software
48 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
52 my $lgpl3_boilerplate=<<"EOL";
53 This program is free software: you can redistribute it and/or modify
54 it under the terms of the GNU Lesser General Public License as published by
55 the Free Software Foundation; either version 3 of the License, or
56 (at your option) any later version.
58 This program is distributed in the hope that it will be useful,
59 but WITHOUT ANY WARRANTY; without even the implied warranty of
60 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
61 GNU General Public License for more details.
63 You should have received a copy of the GNU General Public License
64 along with this program. If not, see <http://www.gnu.org/licenses/>.
67 # license overrides as specified in modules/*
68 our $module_licenses = {
75 license_text => "This file is free software; the Free Software Foundation\n" .
76 "gives unlimited permission to copy and/or distribute it,\n" .
77 "with or without modifications, as long as this notice is preserved.\n",
81 license_text => $lgpl3_boilerplate,
85 license_text => $lgpl2_boilerplate,
89 license_text => $lgpl3_boilerplate,
91 "unmodifiable license text" => {
93 license_text => "Everyone is permitted to copy and distribute verbatim copies\n" .
94 "of this license document, but changing it is not allowed.\n",
96 "GPLed build tool" => {
98 license_text => $gpl_boilerplate,
102 license_text => $gpl_boilerplate,
110 our @deleted_files=();
116 my $writecopyright=0;
118 usage() unless(@ARGV);
119 usage() unless GetOptions("scan|s" => \$scan,
120 "merge|m" => \$merge,
121 "write|w" => \$writecopyright,
128 write_copyright() if ($merge || $writecopyright);
134 for my $file (@filenames)
151 unless(keys(%$files))
153 die("$me: no files known, run $0 --scan\n");
155 unless(copy($COPYRIGHTSTUB, "debian/copyright"))
157 die("$me: cannot copy $COPYRIGHTSTUB to debian/copyright: $!\n");
159 unless(open(COPYRIGHT, ">>debian/copyright"))
161 die("$me: cannot append to debian/copyright: $!\n");
164 # group files by license/license_text/copyright
166 for my $file (sort keys(%$files))
168 my $license=$files->{$file}->{license_override} || $files->{$file}->{license};
169 my $copyright=$files->{$file}->{copyright};
170 my $license_text=$files->{$file}->{license_text_override} ||
171 $files->{$file}->{license_text};
172 push(@{$licenses->{$license}->{$license_text}->{$copyright}}, $file);
176 print COPYRIGHT license_trailer(sort keys(%$licenses));
177 for my $license (sort keys(%$licenses))
179 for my $license_text (sort keys(%{$licenses->{$license}}))
181 my $licensestr=$license;
182 if(length($license_text))
185 # license_text + empty license = License: other
186 if(!length($license))
188 $licensestr = "other";
190 $licensestr .= " [REF$refnum]";
191 $refs{$licensestr}=$license_text;
195 if(!length($license)) {
196 $licensestr="unknown";
199 for my $copyright (sort keys(%{$licenses->{$license}->{$license_text}}))
201 next if(!length($license) && !length($copyright) && !length($license_text));
202 my @filelist=sort @{$licenses->{$license}->{$license_text}->{$copyright}};
203 print COPYRIGHT "Files: ", join(' ', @filelist), "\n";
204 print COPYRIGHT "Copyright: ". (length($copyright) ? $copyright : "unknown" ) . "\n";
205 print COPYRIGHT "License: $licensestr\n" if length($licensestr);
206 print COPYRIGHT "\n";
210 for my $ref (sort byref keys(%refs))
212 print COPYRIGHT "License: $ref\n";
213 my @text=split(/\n/, $refs{$ref});
214 @text=map { ($_ eq "") ? "." : $_; } @text;
215 print COPYRIGHT map { " " . $_ . "\n" } @text;
216 print COPYRIGHT "\n";
223 my $aref=($a=~/\[REF(\d+)\]/)[0];
224 my $bref=($b=~/\[REF(\d+)\]/)[0];
227 return($aref <=> $bref);
234 my @licenses_used=@_;
236 "Apache-2.0" => "Apache License Version 2.0",
237 "Artistic" => "Artistic License",
238 "GFDL-1.2" => "GNU Free Documentation License Version 1.2",
239 "GFDL-1.3" => "GNU Free Documentation License Version 1.3",
240 "GFDL" => "GNU Free Documentation License",
241 "GPL-2" => "GNU General Public License Version 2",
242 "GPL-3" => "GNU General Public License Version 3",
243 "GPL" => "GNU General Public License",
244 "LGPL-2" => "GNU Library General Public License Version 2",
245 "LGPL-2.1" => "GNU Lesser General Public License Version 2.1",
246 "LGPL-3" => "GNU Lesser General Public License Version 3",
247 "LGPL" => "GNU Lesser General Public License",
251 for my $type (reverse sort keys(%$license_data))
253 for my $license (@licenses_used)
255 if($license =~ /$type(\+|\s|$)/i)
257 $types_found{$type}=1;
262 # if just one, use standard style
263 if(keys(%types_found) == 1)
265 my ($file, $name)=each(%types_found);
266 $text .= " The complete text of the $name can be\n";
267 $text .= " found in /usr/share/common-licenses/$file\n";
271 # more than one, use table.
272 $text .= " The complete text of standard licenses referenced above\n";
273 $text .= " can be found in /usr/share/common-licenses/ as follows:\n .\n ";
274 $text .= sprintf("%-60s %s\n", "LICENSE", "FILE");
275 for my $type (sort keys(%types_found))
277 $text .= sprintf(" %-60s %s\n", $license_data->{$type}, $type);
290 if(open(LICENSECHECK, "licensecheck --copyright \"$file\"|"))
292 while(<LICENSECHECK>)
305 $copyright =~ s/^\s*Copyright\s*:\s*//;
306 $license =~ s/.*UNKNOWN.*//;
307 $license =~ s/(L?GPL) \(v([\.\d]+) or later\)/$1-$2+/i;
308 $license =~ s/(L?GPL) \(v([\.\d]+)\)/$1-$2/i;
309 $license =~ s/G?FDL \(v([\.\d]+) or later\)/GFDL-$1+/i;
310 $license =~ s/G?FDL \(v([\.\d]+)\)/GFDL-$1/i;
311 $license =~ s/Apache \(v([\.\d]+) or later\)/Apache-$1+/i;
312 $license =~ s/Apache \(v([\.\d]+)\)/Apache-$1+/i;
314 return($license, $copyright);
320 unless(open(FILE, $filename))
322 warn("$me: $filename: cannot open: $!\n");
326 for(my $i=0; $i < 15; $i++)
333 my $hash=sha256_hex($header);
334 if( (!exists($files->{$filename})) ||
335 ($files->{$filename}->{hash} ne $hash))
337 filechanged($filename, $hash, $header);
344 my($filename, $hash, $header)=@_;
345 my($license_guess, $copyright_guess)=guess_license($filename);
348 license=>$license_guess,
349 copyright=>$copyright_guess,
352 if(exists($files->{$filename}))
354 if(exists($files->{$filename}->{copyright}))
356 $new->{$filename}->{copyright}=$files->{$filename}->{copyright};
357 $new->{$filename}->{copyright_guess}=$copyright_guess;
359 if(exists($files->{$filename}->{license}))
361 $new->{$filename}->{license}=$files->{$filename}->{license};
362 $new->{$filename}->{license_guess}=$license_guess;
364 if(exists($files->{$filename}->{license_text}))
366 $new->{$filename}->{license_text}=$files->{$filename}->{license_text};
373 find(\&wanted_files, ".");
378 if(/^\.git/ || /^\.cvs/ || /^debian/ || /^modules$/ || /^\.pc/)
380 $File::Find::prune=1;
384 push(@filenames, $File::Find::name);
390 if(/^\.[^\/]/ || /^README$/ || /^COPYING$/)
392 $File::Find::prune=1;
397 unless(open(MOD, $File::Find::name))
399 warn("$me: cannot open $File::Find::name: $!\n");
410 $infiles = $inlicense = 0;
414 push(@{$overrides{$_}},@files);
436 find({ wanted => \&wanted_modules, no_chdir => 1}, "modules/");
437 for my $license (keys(%overrides))
439 if(!exists($module_licenses->{$license}))
441 die("$me: license override \"$license\" not found in \$module_licenses\n");
443 my @overridden_files=map { "./" . $_; } @{$overrides{$license}};
444 for my $file (@overridden_files)
446 my $override=$module_licenses->{$license};
447 if(length($override->{license}))
449 $files->{$file}->{license_override}=$override->{license};
451 if(length($override->{license_text}))
453 $files->{$file}->{license_text_override}=$override->{license_text};
462 unless(open(YAML,$FILESCACHE))
464 warn("$me: cannot load cache $FILESCACHE: $!\n");
479 unless(open(YAML,">$FILESCACHE"))
481 warn("$me: cannot save cache $FILESCACHE: $!\n");
484 print YAML Dump($files);
493 warn("$me: no new/changed files found\n");
495 unless(open(NEW,">$NEWFILES"))
497 die("$me: cannot write to $NEWFILES: $!\n");
499 for my $file (sort keys %$new)
501 print NEW "File: $file\n";
502 print NEW "Hash: ", $new->{$file}->{hash}, "\n";
503 print NEW "Copyright: ", $new->{$file}->{copyright}, "\n";
504 if($new->{$file}->{copyright_guess})
506 print NEW "#Copyright_guess: ", $new->{$file}->{copyright_guess}, "\n";
508 print NEW "License: ", $new->{$file}->{license}, "\n";
509 if($new->{$file}->{license_guess})
511 print NEW "#License_guess: ", $new->{$file}->{license_guess}, "\n";
513 if($new->{$file}->{license_text})
515 my @text=split(/\n/, $new->{$file}->{license_text});
516 print NEW "\t" . join("\n\t", @text), "\n";
518 print NEW "#Header: \n";
519 my @headerlines=split(/\n/, $new->{$file}->{header});
520 @headerlines=map { "#" . $_ } grep { defined $_; } @headerlines;
521 print NEW join("\n", @headerlines);
526 print NEW map { "Deleted: $_\n"; } @deleted_files;
533 unless(open(NEW, $NEWFILES))
535 die("$me: $NEWFILES: cannot open: $!\n");
542 my $in_license_text=0;
549 if($in_license_text && /^\s+(.*)/)
551 $license_text .= $1 . "\n";
557 elsif(/^File:\s*(.*)/)
560 # save previous entry
561 if(length($file) && length($hash))
563 $files->{$file}={ hash=>$hash,
564 copyright=>$copyright,
566 license_text=>$license_text };
573 $in_license_text = 0;
575 elsif(/^Hash:\s*(.*)/)
579 elsif(/^Copyright:\s*(.*)/)
583 elsif(/^License:\s*(.*)/)
589 elsif(/^Deleted:\s*(.*)/)
591 if(exists($files->{$1}))
593 delete($files->{$1});
598 warn("$me: $NEWFILES: line $line not recognized\n");
603 if(length($file) && length($hash))
605 $files->{$file}={ hash=>$hash,
606 copyright=>$copyright,
608 license_text=>$license_text };
614 my %newnames = map { $_ => 1 } @filenames;
615 for my $file (sort keys(%$files))
617 unless(exists($newnames{$file}))
619 push(@deleted_files, $file);
624 print "Removed files:\n";
625 print join("\n", @deleted_files),"\n";
642 die("usage: $me [--scan] [--merge]\n",
643 "scans for changed copyright/licenses\n",
644 " -s|-scan Scan for new files & files with changed copyright headers\n",
645 " Writes to debian/clscan/new.txt for manual review.\n",
646 " -m|--merge Merges new data from debian/clscan/new.txt\n",
647 " -w|--write Writes updated debian/copyright.\n",
648 " --merge implies --write.\n");