~ubuntu-branches/debian/sid/libconfig-model-dpkg-perl/sid

« back to all changes in this revision

Viewing changes to lib/Config/Model/Dpkg/Copyright.pm

  • Committer: Package Import Robot
  • Author(s): Dominique Dumont
  • Date: 2015-05-08 10:24:03 UTC
  • mfrom: (1.1.3 experimental)
  • Revision ID: package-import@ubuntu.com-20150508102403-5a61zoujj1kqd7vw
Tags: 2.063
* Copyright scanner:
  * to cope with owner containing 'f00' (Closes: #783932)
  * handle ranges like 2010-12 or 2002-3 (Closes: #783928)
* Dpkg::Patch: fix handling of Subject body expressed as
  free form text below header.
* Updated list of supported arch in C::M::Dpkg::Dependency
  (Closes: #782995)
* control: added build-dep on libyaml-tiny-perl

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package Config::Model::Dpkg::Copyright ;
 
2
 
 
3
use strict;
 
4
use warnings;
 
5
 
 
6
use 5.20.0;
 
7
use IO::Pipe;
 
8
 
 
9
use feature qw/postderef signatures/;
 
10
no warnings qw/experimental::postderef experimental::signatures/;
 
11
 
 
12
use base qw/Config::Model::Node/;
 
13
use Path::Tiny;
 
14
use Data::Dumper;
 
15
 
 
16
use Config::Model::DumpAsData;
 
17
use Dpkg::Copyright::Scanner qw/scan_files __squash __pack_files __create_tree_leaf_from_paths/;
 
18
use Software::LicenseUtils;
 
19
use Scalar::Util qw/weaken/;
 
20
use Storable qw/dclone/;
 
21
 
 
22
my $join_path = "\n "; # used to group Files
 
23
 
 
24
sub get_joined_path ($self, $paths) {
 
25
    return join ($join_path, sort @$paths);
 
26
}
 
27
 
 
28
sub split_path ($self,$path) {
 
29
    return  sort ( ref $path ? @$path : split ( /[\s\n]+/ , $path ) );
 
30
}
 
31
sub normalize_path ($self,$path) {
 
32
    my @paths = $self->split_path($path);
 
33
    return $self->get_joined_path(\@paths);
 
34
}
 
35
 
 
36
my $dumper = Config::Model::DumpAsData->new;
 
37
 
 
38
# $args{in} can contains the output of licensecheck (for tests)
 
39
sub update ($self, %args) {
 
40
 
 
41
    my $files_obj = $self->grab("Files");
 
42
 
 
43
    # explode existing path data to track deleted paths
 
44
    my %old_split_files;
 
45
    my %debian_paths;
 
46
    foreach my $paths_str ($files_obj->fetch_all_indexes) {
 
47
        my $node = $files_obj->fetch_with_id($paths_str) ;
 
48
        my $data = $dumper->dump_as_data( node => $node );
 
49
 
 
50
        if ($paths_str =~ m!^debian/!) {
 
51
            $debian_paths{$paths_str} = $data;
 
52
        }
 
53
        else {
 
54
            foreach my $path ($self->split_path($paths_str)) {
 
55
                $old_split_files{$path} = $data ;
 
56
            }
 
57
        }
 
58
    }
 
59
 
 
60
    my ($files, $copyrights_by_id) = scan_files( %args );
 
61
 
 
62
    # explode new data and merge with existing entries
 
63
    my %new_split_files;
 
64
    my @data;
 
65
    my %data_keys;
 
66
    foreach my $path ( keys $files->%* ) {
 
67
        my ($c, $l) = $copyrights_by_id->[ $files->{$path} ]->@*;
 
68
 
 
69
        my $new_data = dclone (delete $old_split_files{$path} || {} );
 
70
        my $old_cop = $new_data->{Copyright};
 
71
        my $old_lic = $new_data->{License}{short_name};
 
72
        # say "load '$path' with '$c' ('$l') old '$old_cop' ('$old_lic')";
 
73
        # clobber old data
 
74
        $new_data->{Copyright} = $c if ($c !~ /no-info-found|UNKNOWN/ or not $old_cop);
 
75
        $new_data->{License}{short_name} = $l if ($l ne 'UNKNOWN');
 
76
 
 
77
        # create an inventory of different file copyright and license data
 
78
        my $dumper = Data::Dumper->new([$new_data])->Sortkeys(1)->Indent(0);
 
79
        my $datum_dump = $dumper->Dump;
 
80
        my $d_key = $data_keys{$datum_dump};
 
81
        if (not defined $d_key) {
 
82
            push @data,$new_data;
 
83
            $d_key = $data_keys{$datum_dump} = $#data;
 
84
        }
 
85
 
 
86
        # explode path in subpaths and store id pointing to copyright data in there
 
87
        __create_tree_leaf_from_paths(\%new_split_files, $path, $d_key);
 
88
    }
 
89
 
 
90
    # at this point:
 
91
    # * @data contains a list of copyright/license data
 
92
    # * %new_split_files contains a tree matching a directory tree where each leaf
 
93
    #   is an integer index referencing
 
94
    #   an entry in @data to get the correct  copyright/license data
 
95
    # * %old_split_files contains paths no longer present. Useful to trace deleted files
 
96
    # implode files entries with same data index
 
97
 
 
98
    __squash(\%new_split_files) ;
 
99
 
 
100
    # pack files by copyright id
 
101
    my @packed = __pack_files(\%new_split_files);
 
102
 
 
103
    # delete existing data in config tree. A more subtle solution to track which entry is
 
104
    # deleted or altered (when individual files are removed, renamed) is too complex.
 
105
    $files_obj->clear;
 
106
 
 
107
    # count license useage to dedice whether to add a global license
 
108
    # or a single entry. Skip unknown or public-domain licenses
 
109
    my %lic_usage_count;
 
110
    map { $lic_usage_count{$_}++ if $_ and not /unknown|public/i}
 
111
        map {split /\s+or\s+/, $data[$_->[0]]->{License}{short_name} // ''; }
 
112
        @packed ;
 
113
 
 
114
    # load new data in config tree
 
115
    foreach my $p (@packed) {
 
116
        my ($id, @paths) = $p->@*;
 
117
        my $datum = dclone($data[$id]);
 
118
        my $path_str = $self->normalize_path(\@paths);
 
119
        my $l = $datum->{License}{short_name};
 
120
 
 
121
        next unless $l ;
 
122
 
 
123
        my $norm_path_str = $self->normalize_path(\@paths);
 
124
 
 
125
        # if full_license is not provided in datum, check global license(s)
 
126
        if (not $datum->{License}{full_license}) {
 
127
            my $ok = 0;
 
128
            my @sub_licenses = split /\s+or\s+/,$l;
 
129
            my $lic_count = 0;
 
130
            my @empty_licenses = grep {
 
131
                my $text = $self->grab_value(qq!License:"$_" text!) ;
 
132
                $ok++ if $text;
 
133
                $lic_count += $lic_usage_count{$_} // 0 ;
 
134
                not $text; # to get list of empty licenses
 
135
            } @sub_licenses;
 
136
 
 
137
            if ($ok ne @sub_licenses) {
 
138
                my $filler = "Please fill license $l from header of @paths";
 
139
                if ($lic_count > 1 ) {
 
140
                    say "Adding dummy global license text for license $l for path @paths";
 
141
                    map { $self->load(qq!License:"$_" text="$filler"!) } @empty_licenses ;
 
142
 
 
143
                }
 
144
                else {
 
145
                    say "Adding dummy license text for license $l for path @paths";
 
146
                    $datum->{License}{full_license} = $filler;
 
147
                }
 
148
            }
 
149
 
 
150
        }
 
151
 
 
152
        $files_obj->fetch_with_id($path_str)->load_data( $datum );
 
153
    }
 
154
 
 
155
    # delete global license without text
 
156
    my $global_lic_obj = $self->fetch_element('License');
 
157
    foreach my $l ($global_lic_obj->fetch_all_indexes) {
 
158
        $global_lic_obj->delete($l)
 
159
            unless $global_lic_obj->fetch_with_id($l)->fetch_element_value('text');
 
160
    }
 
161
 
 
162
    # put back debian data
 
163
    foreach my $deb_path (sort keys %debian_paths) {
 
164
        $files_obj->fetch_with_id($deb_path)->load_data( $debian_paths{$deb_path} );
 
165
    }
 
166
 
 
167
    my $current_dir = $args{from_dir} || path('.');
 
168
 
 
169
    # warn about old files
 
170
    foreach my $old_path (sort keys %old_split_files) {
 
171
        # put back data matching an existing dir
 
172
        if ($old_path eq '*' or ($old_path =~ m!(.*)/\*$! and $current_dir->is_dir($1))) {
 
173
            say "Note: preserving entry '$old_path'";
 
174
            $files_obj->fetch_with_id($old_path)->load_data( $old_split_files{$old_path} );
 
175
        }
 
176
        else {
 
177
            say "Note: '$old_path' was removed from new upstream source";
 
178
        }
 
179
    }
 
180
 
 
181
    # read a debian/fix.scanned.copyright file to patch scanned data
 
182
    my $debian = $current_dir->child('debian'); # may be missing in test environment
 
183
    if ($debian->is_dir) {
 
184
        $debian->children(qr/fix\.scanned\.copyright$/);
 
185
        my @fixes = $current_dir->child('debian')->children(qr/fix\.scanned\.copyright$/);
 
186
        say "Note: loading @fixes fixes from copyright fix files" if @fixes;
 
187
        foreach my $fix ( @fixes) {
 
188
            my @l = grep { /[^\s]/ } grep { ! m!^(#|//)!  } $fix->lines_utf8;
 
189
            $self->load( join('',@l) );
 
190
        }
 
191
    }
 
192
 
 
193
    # normalized again after all the modifications
 
194
    $self->load("Files:.sort");
 
195
 
 
196
    return ''; # improve returned message ?
 
197
}
 
198
 
 
199
 
 
200
sub fill_global_license ($self, $l, $text) {
 
201
 
 
202
    #say "Adding global license $l";
 
203
    # handle the case where license is something like GPL-2 or GPL-3
 
204
    my @names = $l =~ / or / ? split / or /, $l : ($l);
 
205
 
 
206
    # try to fill text of a known license
 
207
    foreach my $name (@names) {
 
208
        my $license_object ;
 
209
        eval {
 
210
            $license_object = Software::LicenseUtils->new_from_short_name( {
 
211
                short_name => $name,
 
212
                holder => 'X. Ample'
 
213
            }) ;
 
214
        };
 
215
        if ($license_object) {
 
216
            $self->load(qq!License:$name!); # model will fill the text
 
217
        }
 
218
        else {
 
219
            $self->load(qq!License:$name text:"$text"!);
 
220
        }
 
221
    }
 
222
}
 
223
 
 
224
1;
 
225
 
 
226
__END__
 
227
 
 
228
 
 
229
=head1 NAME
 
230
 
 
231
Config::Model::Dpkg::Copyright - Fill the File sections of debian/copyright file
 
232
 
 
233
=head1 SYNOPSIS
 
234
 
 
235
 # this modules is used by cme when invoked with this command
 
236
 $ cme update dpkg-copyright
 
237
 
 
238
=head1 DESCRIPTION
 
239
 
 
240
This commands helps with the tedious task of maintening
 
241
C<debian/copyright> file. When you package a new release of a
 
242
software, you can run C<cme update dpkg-copyright> to update the
 
243
content of the copyright file.
 
244
 
 
245
This command scans current package directory to extract copyright and
 
246
license information and store them in the Files sections of
 
247
debian/copyright file.
 
248
 
 
249
In debian package directory:
 
250
 
 
251
* run 'cme update dpkg-copyright' or 'cme update dpkg'
 
252
* check the result with your favorite VCS diff tool. (you do use
 
253
  a VCS for your package files, do you ?)
 
254
 
 
255
Note: this command is experimental.
 
256
 
 
257
=head1 Tweak results
 
258
 
 
259
Since the extraction of copyright information from source file is
 
260
based on comments, the result is sometimes lackluster. Your may
 
261
specify instruction to alter or set specific copyright entries in
 
262
C<debian/fix.scanned.copyright> file
 
263
(or C<< debian/<source-package>.fix.scanned.copyright >>).
 
264
Each line of this file will be handled
 
265
by L<Config::Model::Loader> to modify copyright information.
 
266
 
 
267
=head2 Example
 
268
 
 
269
If the extracted copyright contains:
 
270
 
 
271
 Files: *
 
272
 Copyright: 2014-2015, Adam Kennedy <adamk@cpan.org> "foobar
 
273
 License: Artistic or GPL-1+
 
274
 
 
275
You may add this line in C<debian/fix.copyright> file:
 
276
 
 
277
 ! Files:'*' Copyright=~s/\s*".*//
 
278
 
 
279
This way, the copyright information will be updated from the file
 
280
content but the extra C<"foobar> will always be removed during
 
281
updates.
 
282
 
 
283
Comments are accepted in Perl and C++ style from the beginning of the line.
 
284
Lines breaks are ignored.
 
285
 
 
286
Here's another more complex example:
 
287
 
 
288
 // added a global license, MIT license text is filled by Config::Model
 
289
 ! copyright License:MIT
 
290
 
 
291
 # don't forget '!' to go back to tree root
 
292
 ! copyright Files:"pan/general/map-vector.h" Copyright="2001,Andrei Alexandrescu"
 
293
   License short_name=MIT
 
294
 # delete license text since short_name points to global  MIT license
 
295
   full_license~
 
296
 
 
297
 # use a loop there vvvvvv to clean up that vvvvvvvvvvvvvvvvvvvvvvv in all copyrights
 
298
 ! copyright   Files:~/.*/     Copyright=~s/all\s*rights\s*reserved//i
 
299
 
 
300
 
 
301
=head1 AUTHOR
 
302
 
 
303
Dominique Dumont <dod@debian.org>
 
304
 
 
305
=cut