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 # FIXME: add boilerplate
21 our %module_licenses= (
22 "public domain" => "",
24 "This file is free software; the Free Software Foundation\n" .
25 "gives unlimited permission to copy and/or distribute it,\n" .
26 "with or without modifications, as long as this notice is preserved.\n",
29 "unmodifiable license text" =>
30 "Everyone is permitted to copy and distribute verbatim copies\n" .
31 "of this license document, but changing it is not allowed.\n",
32 "GPLed build tool" => "",
45 usage() unless(@ARGV);
46 usage() unless GetOptions("scan|s" => \$scan,
48 "write|w" => \$writecopyright,
49 "help|h" => sub { usage(); });
54 write_copyright() if ($merge || $writecopyright);
59 for my $file (@filenames)
78 die("$me: no files known, run $0 --scan\n");
80 unless(copy($COPYRIGHTSTUB, "debian/copyright"))
82 die("$me: cannot copy $COPYRIGHTSTUB to debian/copyright: $!\n");
84 unless(open(COPYRIGHT, ">>debian/copyright"))
86 die("$me: cannot append to debian/copyright: $!\n");
89 # group files by license/license_text/copyright
91 for my $file (sort keys(%$files))
93 my $license=$files->{$file}->{license};
94 my $copyright=$files->{$file}->{copyright};
95 my $license_text=$files->{$file}->{license_text};
96 push(@{$licenses->{$license}->{$license_text}->{$copyright}}, $file);
100 for my $license (sort keys(%$licenses))
102 for my $license_text (sort keys(%{$licenses->{$license}}))
104 my $licensestr=$license;
105 if(length($license_text))
108 # license_text + empty license = License: other
109 if(!length($license))
111 $licensestr = "other";
113 $licensestr .= " [REF$refnum]";
114 $refs{$licensestr}=$license_text;
116 for my $copyright (sort keys(%{$licenses->{$license}->{$license_text}}))
118 next if(!length($license) && !length($copyright) && !length($license_text));
119 my @filelist=sort @{$licenses->{$license}->{$license_text}->{$copyright}};
120 print COPYRIGHT "Files: ", join(', ', @filelist), "\n";
121 print COPYRIGHT "Copyright: $copyright\n" if length($copyright);
122 print COPYRIGHT "License: $licensestr\n" if length($licensestr);
123 print COPYRIGHT "\n";
127 for my $ref (sort keys(%refs))
129 print COPYRIGHT "License: $ref\n";
130 my @text=split(/\n/, $refs{$ref});
131 print COPYRIGHT map { " " . $_ . "\n" } @text;
132 print COPYRIGHT "\n";
134 print COPYRIGHT license_trailer(sort keys(%$licenses));
140 my @licenses_used=@_;
142 "Apache-2.0" => "Apache License Version 2.0",
143 "Artistic" => "Artistic License",
144 "GFDL-1.2" => "GNU Free Documentation License Version 1.2",
145 "GFDL-1.3" => "GNU Free Documentation License Version 1.3",
146 "GFDL" => "GNU Free Documentation License",
147 "GPL-2" => "GNU General Public License Version 2",
148 "GPL-3" => "GNU General Public License Version 3",
149 "GPL" => "GNU General Public License",
150 "LGPL-2" => "GNU Library General Public License Version 2",
151 "LGPL-2.1" => "GNU Lesser General Public License Version 2.1",
152 "LGPL-3" => "GNU Lesser General Public License Version 3",
153 "LGPL" => "GNU Lesser General Public License",
157 for my $type (reverse sort keys(%$license_data))
159 for my $license (@licenses_used)
161 if($license =~ /$type(\+|\s|$)/i)
163 $types_found{$type}=1;
164 print "FOUND $type [$license]\n";
169 # if just one, use standard style
170 if(keys(%types_found) == 1)
172 my ($file, $name)=each(%types_found);
173 $text .= "The complete text of the $name can be\n";
174 $text .= "found in /usr/share/common-licenses/$file\n";
178 # more than one, use table.
179 $text .= "The complete text of standard licenses referenced above\n";
180 $text .= "can be found in /usr/share/common-licenses/ as follows:\n\n";
181 $text .= sprintf("%-60s %s\n", "LICENSE", "FILE");
182 for my $type (sort keys(%types_found))
184 $text .= sprintf("%-60s %s\n", $license_data->{$type}, $type);
196 if(open(LICENSECHECK, "licensecheck --copyright \"$file\"|"))
198 while(<LICENSECHECK>)
211 $copyright =~ s/^\s*Copyright\s*:\s*//;
212 $license =~ s/.*UNKNOWN.*//;
213 $license =~ s/(L?GPL) \(v([\.\d]+) or later\)/$1-$2+/i;
214 $license =~ s/(L?GPL) \(v([\.\d]+)\)/$1-$2/i;
215 $license =~ s/G?FDL \(v([\.\d]+) or later\)/GFDL-$1+/i;
216 $license =~ s/G?FDL \(v([\.\d]+)\)/GFDL-$1/i;
217 $license =~ s/Apache \(v([\.\d]+) or later\)/Apache-$1+/i;
218 $license =~ s/Apache \(v([\.\d]+)\)/Apache-$1+/i;
220 return($license, $copyright);
226 unless(open(FILE, $filename))
228 warn("$me: $filename: cannot open: $!\n");
232 for(my $i=0; $i < 15; $i++)
239 my $hash=sha256_hex($header);
240 if( (!exists($files->{$filename})) ||
241 ($files->{$filename}->{hash} ne $hash))
243 filechanged($filename, $hash, $header);
250 my($filename, $hash, $header)=@_;
251 my($license_guess, $copyright_guess)=guess_license($filename);
254 license=>$license_guess,
255 copyright=>$copyright_guess,
258 if(exists($files->{$filename}))
260 if(exists($files->{$filename}->{copyright}))
262 $new->{$filename}->{copyright_old}=$files->{$filename}->{copyright};
264 if(exists($files->{$filename}->{license}))
266 $new->{$filename}->{license_old}=$files->{$filename}->{license};
268 if(exists($files->{$filename}->{license_text}))
270 $new->{$filename}->{license_text_old}=$files->{$filename}->{license_text};
277 find(\&wanted_files, ".");
282 if(/^\.git/ || /^\.cvs/ || /^debian/ || /^modules$/)
284 $File::Find::prune=1;
288 push(@filenames, $File::Find::name);
294 if(/^\.[^\/]/ || /^README$/ || /^COPYING$/)
296 $File::Find::prune=1;
301 unless(open(MOD, $File::Find::name))
303 warn("$me: cannot open $File::Find::name: $!\n");
314 $infiles = $inlicense = 0;
318 push(@{$overrides{$_}},@files);
340 find({ wanted => \&wanted_modules, no_chdir => 1}, "modules/");
341 for my $license (keys(%overrides))
343 # print("License: $license\n");
344 # print("Files: \n\t");
345 # print(join("\n\t", @{$overrides{$license}}),"\n");
352 unless(open(YAML,$FILESCACHE))
354 warn("$me: cannot load cache $FILESCACHE: $!\n");
369 unless(open(YAML,">$FILESCACHE"))
371 warn("$me: cannot save cache $FILESCACHE: $!\n");
374 print YAML Dump($files);
383 warn("$me: no new/changed files found\n");
386 unless(open(NEW,">$NEWFILES"))
388 die("$me: cannot write to $NEWFILES: $!\n");
390 for my $file (sort keys %$new)
392 print NEW "File: $file\n";
393 print NEW "Hash: ", $new->{$file}->{hash}, "\n";
394 print NEW "Copyright: ", $new->{$file}->{copyright}, "\n";
395 print NEW "License: ", $new->{$file}->{license}, "\n";
396 print NEW "License_Text: \n";
397 if($new->{$file}->{license_old})
399 print NEW "#License_old: ", $new->{$file}->{license_old}, "\n";
401 if($new->{$file}->{copyright_old})
403 print NEW "#Copyright_old: ", $new->{$file}->{copyright_old}, "\n";
405 if($new->{$file}->{licence_text_old})
407 print NEW "#License_text_old: ", $new->{$file}->{licence_text_old}, "\n";
409 print NEW "#Header: \n";
410 my @headerlines=split(/\n/, $new->{$file}->{header});
411 @headerlines=map { "#" . $_ } grep { defined $_; } @headerlines;
412 print NEW join("\n", @headerlines);
420 unless(open(NEW, $NEWFILES))
422 die("$me: $NEWFILES: cannot open: $!\n");
429 my $in_license_text=0;
436 if($in_license_text && /^\s+(.*)/)
438 $license_text .= $1 . "\n";
444 elsif(/^File:\s*(.*)/)
447 # save previous entry
448 if(length($file) && length($hash))
450 $files->{$file}={ hash=>$hash,
451 copyright=>$copyright,
453 license_text=>$license_text };
460 $in_license_text = 0;
462 elsif(/^Hash:\s*(.*)/)
466 elsif(/^Copyright:\s*(.*)/)
470 elsif(/^License:\s*(.*)/)
478 warn("$me: $NEWFILES: line $line not recognized\n");
483 if(length($file) && length($hash))
485 $files->{$file}={ hash=>$hash,
486 copyright=>$copyright,
488 license_text=>$license_text };
495 my %newnames = map { $_ => 1 } @filenames;
496 for my $file (sort keys(%$files))
498 unless(exists($newnames{$file}))
500 push(@deleted, $file);
505 print "Removed files:\n";
506 print join("\n", @deleted),"\n";
523 die("usage: $me [--scan] [--merge]\n",
524 "scans for changed copyright/licenses\n",
525 " -s|-scan Scan for new files & files with changed copyright headers\n",
526 " Writes to debian/clscan/new.txt for manual review.\n",
527 " -m|--merge Merges new data from debian/clscan/new.txt\n",
528 " -w|--write Writes updated debian/copyright.\n",
529 " --merge implies --write.\n");