Commit | Line | Data |
---|---|---|
418f4069 A |
1 | #!/usr/bin/perl |
2 | ||
3 | # | |
4 | # Script to help out with syncing cpan distros. | |
5 | # | |
6 | # Does the following: | |
7 | # - Fetches the package list from CPAN. Finds the current version of | |
8 | # the given package. | |
9 | # - Downloads the relevant tarball; unpacks the tarball;. | |
10 | # - Clean out the old directory (git clean -dfx) | |
11 | # - Moves the old directory out of the way, moves the new directory in place. | |
12 | # - Restores any .gitignore file. | |
13 | # - Removes files from @IGNORE and EXCLUDED | |
14 | # - git add any new files. | |
15 | # - git rm any files that are gone. | |
16 | # - Remove the +x bit on files in t/ | |
ad9b4e6f | 17 | # - Remove the +x bit on files that don't have in enabled in the current dir |
418f4069 A |
18 | # - Adds new files to MANIFEST |
19 | # - Runs a "make" (assumes a configure has been run) | |
20 | # - Cleans up | |
ad9b4e6f | 21 | # - Runs tests for the package |
418f4069 A |
22 | # - Runs the porting tests |
23 | # | |
24 | # TODO: - Restore files from CUSTOMIZED | |
25 | # - Delete files from MANIFEST | |
26 | # - Update Porting/Maintainers.pl | |
418f4069 | 27 | # - Optional, run a full test suite |
6f865444 | 28 | # - Handle complicated FILES |
418f4069 A |
29 | # |
30 | # This is an initial version; no attempt has been made yet to make this | |
31 | # portable. It shells out instead of trying to find a Perl solution. | |
32 | # In particular, it assumes wget, git, tar, chmod, perl, make, and rm | |
33 | # to be available. | |
34 | # | |
35 | # Usage: perl Porting/sync-with-cpan <module> | |
36 | # where <module> is the name it appears in the %Modules hash | |
37 | # of Porting/Maintainers.pl | |
38 | # | |
39 | ||
40 | use 5.010; | |
41 | ||
42 | use strict; | |
43 | use warnings; | |
44 | no warnings 'syntax'; | |
45 | ||
46 | $| = 1; | |
47 | ||
48 | die "This does not like top level directory" | |
49 | unless -d "cpan" && -d "Porting"; | |
50 | ||
51 | package Maintainers; | |
52 | ||
53 | our @IGNORABLE; | |
54 | our %Modules; | |
55 | ||
56 | use autodie; | |
57 | ||
58 | require "Porting/Maintainers.pl"; | |
59 | ||
60 | chdir "cpan"; | |
61 | ||
62 | my %IGNORABLE = map {$_ => 1} @IGNORABLE; | |
63 | ||
64 | my $package = "02packages.details.txt"; | |
65 | my $package_url = "http://www.cpan.org/modules/$package"; | |
66 | my $package_file = "/tmp/$package"; | |
67 | ||
68 | # | |
69 | # Poor man's cache | |
70 | # | |
71 | unless (-f $package_file && -M $package_file < 1) { | |
72 | system wget => $package_url, '-qO', $package_file; | |
73 | } | |
74 | ||
75 | die "Usage: $0 module" unless @ARGV == 1; | |
76 | ||
77 | my ($module) = @ARGV; | |
78 | ||
79 | my $info = $Modules {$module} or die "Cannot find module $module"; | |
80 | my $distribution = $$info {DISTRIBUTION}; | |
81 | my $pkg_dir = $$info {FILES}; | |
82 | $pkg_dir =~ s!.*/!!; | |
83 | ||
84 | my ($old_version) = $distribution =~ /-([0-9.]+)\.tar\.gz/; | |
85 | ||
86 | my $o_module = $module; | |
87 | if ($module =~ /-/ && $module !~ /::/) { | |
88 | $module =~ s/-/::/g; | |
89 | } | |
90 | ||
91 | # | |
92 | # Find the information from CPAN. | |
93 | # | |
94 | my $new_line = `grep '^$module ' $package_file` | |
95 | or die "Cannot find $module on CPAN\n"; | |
96 | chomp $new_line; | |
97 | my (undef, $new_version, $new_path) = split ' ', $new_line; | |
98 | my $new_file = (split '/', $new_path) [-1]; | |
99 | ||
100 | my $old_dir = "$pkg_dir-$old_version"; | |
101 | my $new_dir = "$pkg_dir-$new_version"; | |
102 | ||
103 | say "Cleaning out old directory"; | |
104 | system git => 'clean', '-dfxq', $pkg_dir; | |
105 | ||
106 | ||
107 | ||
108 | my $url = "http://search.cpan.org/CPAN/authors/id/$new_path"; | |
109 | ||
110 | say "Fetching $url"; | |
111 | ||
112 | # | |
113 | # Fetch the new distro | |
114 | # | |
115 | system wget => $url, '-qO', $new_file; | |
116 | ||
117 | say "Unpacking $new_file"; | |
118 | ||
119 | system tar => 'xfz', $new_file; | |
120 | ||
121 | say "Renaming directories"; | |
122 | rename $pkg_dir => $old_dir; | |
123 | rename $new_dir => $pkg_dir; | |
124 | ||
125 | ||
126 | if (-f "$old_dir/.gitignore") { | |
127 | say "Restoring .gitignore"; | |
128 | system git => 'checkout', "$pkg_dir/.gitignore"; | |
129 | } | |
130 | ||
ad9b4e6f | 131 | my @new_files = `find $pkg_dir -type f`; |
418f4069 A |
132 | chomp @new_files; |
133 | @new_files = grep {$_ ne $pkg_dir} @new_files; | |
134 | s!^[^/]+/!! for @new_files; | |
135 | my %new_files = map {$_ => 1} @new_files; | |
136 | ||
ad9b4e6f | 137 | my @old_files = `find $old_dir -type f`; |
418f4069 A |
138 | chomp @old_files; |
139 | @old_files = grep {$_ ne $old_dir} @old_files; | |
140 | s!^[^/]+/!! for @old_files; | |
141 | my %old_files = map {$_ => 1} @old_files; | |
142 | ||
143 | # | |
144 | # Find files that can be deleted. | |
145 | # | |
146 | my @EXCLUDED_QR; | |
147 | my %EXCLUDED_QQ; | |
148 | if ($$info {EXCLUDED}) { | |
149 | foreach my $entry (@{$$info {EXCLUDED}}) { | |
150 | if (ref $entry) {push @EXCLUDED_QR => $entry} | |
151 | else {$EXCLUDED_QQ {$entry} = 1} | |
152 | } | |
153 | } | |
154 | ||
155 | my @delete; | |
156 | my @commit; | |
157 | my @gone; | |
418f4069 A |
158 | FILE: |
159 | foreach my $file (@new_files) { | |
160 | next if -d "$pkg_dir/$file"; # Ignore directories. | |
161 | next if $old_files {$file}; # It's already there. | |
162 | if ($IGNORABLE {$file}) { | |
163 | push @delete => $file; | |
164 | next; | |
165 | } | |
166 | if ($EXCLUDED_QQ {$file}) { | |
167 | push @delete => $file; | |
168 | next; | |
169 | } | |
170 | foreach my $pattern (@EXCLUDED_QR) { | |
171 | if ($file =~ /$pattern/) { | |
172 | push @delete => $file; | |
173 | next FILE; | |
174 | } | |
175 | } | |
176 | push @commit => $file; | |
177 | } | |
178 | foreach my $file (@old_files) { | |
179 | next if -d "$old_dir/$file"; | |
180 | next if $new_files {$file}; | |
181 | push @gone => $file; | |
182 | } | |
ad9b4e6f A |
183 | |
184 | # | |
185 | # Find all files with an exec bit | |
186 | # | |
187 | my @exec = `find $pkg_dir -type f -perm +111`; | |
418f4069 | 188 | chomp @exec; |
ad9b4e6f A |
189 | my @de_exec; |
190 | foreach my $file (@exec) { | |
191 | # Remove leading dir | |
192 | $file =~ s!^[^/]+/!!; | |
193 | if ($file =~ m!^t/!) { | |
194 | push @de_exec => $file; | |
195 | next; | |
196 | } | |
197 | # Check to see if the file exists; if it doesn't and doesn't have | |
198 | # the exec bit, remove it. | |
199 | if ($old_files {$file}) { | |
200 | unless (-x "$old_dir/$file") { | |
201 | push @de_exec => $file; | |
202 | } | |
203 | } | |
204 | } | |
418f4069 A |
205 | |
206 | # | |
207 | # No need to change the +x bit on files that will be deleted. | |
208 | # | |
ad9b4e6f | 209 | if (@de_exec && @delete) { |
a9f5d1d4 | 210 | my %delete = map {+"$pkg_dir/$_" => 1} @delete; |
ad9b4e6f | 211 | @de_exec = grep {!$delete {$_}} @de_exec; |
418f4069 A |
212 | } |
213 | ||
214 | say "unlink $pkg_dir/$_" for @delete; | |
215 | say "git add $pkg_dir/$_" for @commit; | |
216 | say "git rm -f $pkg_dir/$_" for @gone; | |
ad9b4e6f | 217 | say "chmod a-x $pkg_dir/$_" for @de_exec; |
418f4069 A |
218 | |
219 | print "Hit return to continue; ^C to abort "; <STDIN>; | |
220 | ||
221 | unlink "$pkg_dir/$_" for @delete; | |
222 | system git => 'add', "$pkg_dir/$_" for @commit; | |
223 | system git => 'rm', '-f', "$pkg_dir/$_" for @gone; | |
ad9b4e6f | 224 | system chmod => 'a-x', "$pkg_dir/$_" for @de_exec; |
418f4069 | 225 | |
a8121781 | 226 | chdir ".."; |
418f4069 A |
227 | if (@commit) { |
228 | say "Fixing MANIFEST"; | |
a8121781 | 229 | my $MANIFEST = "MANIFEST"; |
418f4069 A |
230 | my $MANIFEST_SORT = "$MANIFEST.sorted"; |
231 | open my $fh, ">>", $MANIFEST; | |
a8121781 | 232 | say $fh "cpan/$pkg_dir/$_" for @commit; |
418f4069 | 233 | close $fh; |
a8121781 | 234 | system perl => "Porting/manisort", '--output', $MANIFEST_SORT; |
418f4069 A |
235 | rename $MANIFEST_SORT => $MANIFEST; |
236 | } | |
237 | ||
238 | ||
418f4069 A |
239 | print "Running a make ... "; |
240 | system "make > make.log 2>&1" and die "Running make failed, see make.log"; | |
241 | print "done\n"; | |
242 | ||
243 | # | |
244 | # Must clean up, or else t/porting/FindExt.t will fail. | |
245 | # Note that we can always retrieve the orginal directory with a git checkout. | |
246 | # | |
247 | print "About to clean up; hit return or abort (^C) "; <STDIN>; | |
248 | ||
249 | chdir "cpan"; | |
250 | system rm => '-r', $old_dir; | |
251 | unlink $new_file; | |
252 | ||
253 | ||
ad9b4e6f A |
254 | # |
255 | # Run the tests. First the test belonging to the module, followed by the | |
256 | # the tests in t/porting | |
257 | # | |
418f4069 | 258 | chdir "../t"; |
ad9b4e6f A |
259 | say "Running module tests"; |
260 | my @test_files = `find ../cpan/$pkg_dir -name '*.t' -type f`; | |
261 | chomp @test_files; | |
262 | my $output = `./perl TEST @test_files`; | |
263 | unless ($output =~ /All tests successful/) { | |
264 | say $output; | |
265 | exit 1; | |
266 | } | |
267 | ||
418f4069 A |
268 | print "Running tests in t/porting "; |
269 | my @tests = `ls porting/*.t`; | |
270 | chomp @tests; | |
271 | my @failed; | |
272 | foreach my $t (@tests) { | |
273 | my @not = `./perl -I../lib -I.. $t | grep ^not | grep -v "# TODO"`; | |
274 | print @not ? '!' : '.'; | |
275 | push @failed => $t if @not; | |
276 | } | |
277 | print "\n"; | |
278 | say "Failed tests: @failed" if @failed; | |
279 | ||
280 | ||
281 | print "Now you ought to run a make; make test ...\n"; | |
282 | ||
283 | say "Do not forget to update Porting/Maintainers.pl before committing"; | |
284 | say "$o_module is now version $new_version"; | |
285 | ||
286 | ||
287 | __END__ |