9 relicense.pl [options] -p <permissions file> <file> [<file>...]
13 -p,--permitted=FILE Specify file of emails with relicensing permission
14 -f,--force Manually force relicensing
15 -h,--help Display brief help message
16 -v,--verbose Increase verbosity
17 -q,--quiet Decrease verbosity
22 use IPC::Run qw ( run );
28 # Parse command-line options
32 Getopt::Long::Configure ( "bundling", "auto_abbrev" );
34 'permitted|p=s' => \$permfile,
36 'verbose|v+' => sub { $verbosity++; },
37 'quiet|q+' => sub { $verbosity--; },
38 'help|h' => sub { pod2usage ( 1 ); },
39 ) or die "Could not parse command-line options";
40 pod2usage ( 1 ) unless @ARGV;
42 # Read permitted emails file
43 my @emails = ( $permfile ? read_file ( $permfile ) : () );
45 my $permitted = { map { /^.*<(\S+)>$/; ( $1 || $_ ) => 1 } @emails };
47 # Define list of relicensable licences
52 # Define blurb to be added to copyright notice
55 * You can also choose to distribute this program under the terms of
56 * the Unmodified Binary Distribution Licence (as given in the file
57 * COPYING.UBDL), provided that you have satisfied its requirements.';
62 while ( my $filename = shift @ARGV ) {
64 # Read file to determine existing licence
65 my $file = read_file ( $filename );
66 my @licences = ( $file =~ /^\s*FILE_LICENCE\s*\(\s*(\S+)\s*\)\s*;?$/mg );
67 die "No licence declaration in $filename\n" unless @licences;
68 die "Multiple licence declarations in $filename\n" if @licences > 1;
69 my $licence = $licences[0];
71 # Skip if file is already UBDL-licensed
72 next if $licence =~ /_OR_UBDL$/;
74 # Fail immediately if file is not a candidate for relicensing
75 if ( ! exists $relicensable->{$licence} ) {
76 print "Non-relicensable licence $licence in $filename\n";
77 push @failed, $filename;
84 run [ "git", "blame", "-M", "-C", "-p", "-w", $filename ],
85 \undef, \$stdout, \$stderr
86 or die "git-blame $filename: $?";
87 die $stderr if $stderr;
90 my @stdout = split ( /\n/, $stdout );
97 my $commit_line = shift @stdout;
98 ( my $commit, undef, my $lineno, undef, my $count ) =
100 /^([0-9a-f]{40})\s+([0-9]+)\s+([0-9]+)(\s+([0-9]+))?$/ )
101 or die "Malformed commit line \"$commit_line\"\n";
103 $details->{$commit} ||= {};
104 while ( ! ( $stdout[0] =~ /^\t/ ) ) {
105 my $detail_line = shift @stdout;
106 ( my $key, undef, my $value ) =
107 ( $detail_line =~ /^([a-z-]+)(\s+(.+))?$/ )
108 or die "Malformed detail line \"$detail_line\" for $commit_line\n";
109 $details->{$commit}->{$key} = $value;
112 die "Missing commit details for $commit_line\n"
113 unless %{$details->{$commit}};
114 my $code_line = shift @stdout;
115 ( my $line ) = ( $code_line =~ /^\t(.*)$/ )
116 or die "Malformed code line \"$code_line\" for $commit_line\n";
118 # Skip trivial lines and lines so common that they are likely to
119 # be misattributed by git-blame
120 next if $line =~ /^\s*$/; # Empty lines
121 next if $line =~ /^\s*\/\*/; # Start of comments
122 next if $line =~ /^\s*\*/; # Middle (or end) of comments
123 next if $line =~ /^\s*\{\s*$/; # Standalone opening braces
124 next if $line =~ /^\s*\};?\s*$/; # Standalone closing braces
125 next if $line =~ /^\#include/; # Header inclusions
126 next if $line =~ /^\s*return\s+0;/; # return 0;
127 next if $line =~ /^\s*return\s+rc;/; # return rc;
128 next if $line =~ /^\s*PCI_ROM\s*\(.*\)\s*,\s*$/; # PCI IDs
129 next if $line =~ /^\s*FILE_LICENCE\s*\(.*\)\s*;$/; # Licence declarations
132 my $author_mail = $details->{$commit}->{"author-mail"}
133 or die "Missing author email for $commit_line\n";
134 ( my $email ) = ( $author_mail =~ /^<(\S+)>$/ )
135 or die "Malformed author email \"$author_mail\" for $commit_line\n";
136 undef $email if exists $details->{$commit}->{boundary};
138 # Check for relicensing permission
139 next if defined $email && exists $permitted->{$email};
141 # Print out lines lacking permission
142 printf $filename."\n" unless $failures;
143 printf "%4d %-30s %s\n", $lineno, ( $email || "<root>" ), $line;
147 # Fail if there are any non-trivial lines lacking relicensing permission
148 if ( $failures && ! $force ) {
149 push @failed, $filename;
153 # Modify FILE_LICENCE() line
154 $file =~ s/(^\s*FILE_LICENCE\s*\(\s*${licence})(\s*\)\s*;?$)/$1_OR_UBDL$2/m
155 or die "Could not modify FILE_LICENCE() in $filename\n";
157 # Modify copyright notice, if present
158 if ( $file =~ /GNU General Public License/i ) {
159 $file =~ s/(02110-1301, USA.$)/$1${blurb}/m
160 or die "Could not modify copyright notice in $filename\n";
163 # Write out modified file
164 write_file ( $filename, { atomic => 1 }, $file );
165 push @succeeded, $filename;
168 print "Relicensed: ".join ( " ", @succeeded )."\n" if @succeeded;
169 die "Cannot relicense: ".join ( " ", @failed )."\n" if @failed;