/[pkg-kde]/scripts/autofixtll
ViewVC logotype

Contents of /scripts/autofixtll

Parent Directory Parent Directory | Revision Log Revision Log


Revision 10311 - (show annotations) (download)
Mon Apr 28 01:00:13 2008 UTC (5 years, 1 month ago) by modax-guest
File size: 17249 byte(s)
autofixtll v0.3

* Deals more carefully with target_link_libraries() enclosed in IFs/WHILEs/etc.
* A few small bugfixes

Recommended usage is:

autofixtll -e
1 #!/usr/bin/perl -w
2
3 =head1 NAME
4
5 B<autofixtll> - auto-correct CMake TARGET_LINK_LIBRARIES directive according to
6 the information provided by GNU ld linker S<I<'undefined references'>> errors.
7
8 =head1 SYNOPSIS
9
10 B<autofixtll> [B<--invoke-edit|-e>] [B<--build-dir|-b>=I<dir>] [B<--build-command|-c>=I<command>] [B<--patch-name|-p>=I<name>] [B<--do-backups>]
11
12 =head1 DESCRIPTION
13
14 B<autofixtll> is capable of correcting most linking failures caused by
15 S<'undefined references'> linker errors. The script should be executed from the
16 extracted debian source tree with all build dependences installed in the
17 environment. This script is a wrapper around S<`debian/rules build'> or
18 whatever command you specify with B<--build-command> option.
19
20 B<autofixtll> depends on B<quilt> to incrementally build up a patch of the
21 changes it does. The script assumes that quilt patches are located in
22 debian/patches. The default name of the patch is
23 S<I<"97_fix_target_link_libraries.diff">> (it can be changed with the
24 [B<--patch-name> option). This patch must have already pushed to the "quilt
25 top" when this script is executed.
26
27 First of all, the script loads dynamic symbol names of the libraries specified
28 in the @LIBS array. Then it starts building process and keeps monitoring it
29 while looking for the "undefined references" errors from the linker (only GNU
30 ld syntax is supported). Then it tries to match undefined symbol names with the
31 symbols loaded from the libraries in the @LIBS array. If matches are found, it
32 tries to update TARGET_LINK_LIBRARIES command in the respective CMakeLists.txt
33 file adding missing libraries. Finally, the build process is restarted.
34
35 This loop continues until build completes successfully or the error which
36 B<autofixtll> can't handle occurs. In latter case, a user will need to `quilt
37 edit' the respective file manually or add more libs to the @LIBS array. Then
38 the script can be restarted again (or use --invoke-edit option to invoke
39 I<quilt edit> and restart build for you automatically).
40
41 CMake verbose output must be enabled for B<autofixtll> to work reliably.
42
43 B<autofixtll> was written to help maintainer resolve build failures of KDE
44 applications which were introduced by the clean up of KDELibs recursive library
45 dependences.
46
47 =head1 OPTIONS
48
49 =over 4
50
51 =item B<-b> I<dir>, B<--build-dir>=I<dir>
52
53 Specify a custom build directory. It must be relative the current (source)
54 directory. Default is as returned by S<I<"obj-`dpkg-architecture
55 -qDEB_BUILD_GNU_TYPE`>">.
56
57 =item B<-c> I<command>, B<--build-command>=I<command>
58
59 The command which should be run to build the source. If the command is not
60 specific, `debian/rules build' will be executed from the B<source tree>.
61 However, if you specify the command, it will be from the B<build tree>.
62
63 =item B<-p>, B<--patch-name>=I<name>
64
65 The name of the quilt patch in which all changes made by the script will be
66 stored. Default is I<97_fix_target_link_libraries.diff>.
67
68 =item B<--do-backups>, B<--backup>
69
70 Whether to backup CMakeLists.txt as CMakeLists.txt.orig before auto-modifying
71 it.
72
73 =item B<--invoke-edit>, B<--edit>, B<-e>
74
75 Invoke `quilt edit' on the respective CMakeLists.txt when undefined references
76 cannot be automatically resolved and restart build process immediately when the
77 editor is closed.
78
79 =back
80
81 =head1 LICENSE
82
83 This program is free software: you can redistribute it and/or modify it under
84 the terms of the GNU General Public License as published by the Free Software
85 Foundation, either version 3 of the License, or (at your option) any later
86 version.
87
88 On B<Debian> systems, the complete text of the GNU GPL v3 can be found in the
89 file F</usr/share/common-licenses/GPL-3>
90
91 =head1 AUTHORS
92
93 Written by Modestas Vainius <modestas@vainius.eu>
94
95 =cut
96
97 use strict;
98 use Cwd qw(getcwd realpath);
99 use File::Spec;
100 use File::Copy;
101 use Getopt::Long;
102 use Pod::Usage;
103 use FileHandle;
104 use IPC::Open2;
105
106 #### Please add predefined libraries to load dynamic symbol from here.
107 # new Library(name, cmake_target, [path]). If path is not specified,
108 # it's /usr/lib/$name.so. Use '' quotes to avoid escaping $.
109 my @LIBS = (
110 new Library('QtDBus', '${QT_QTDBUS_LIBRARY}'),
111 new Library('QtNetwork', '${QT_QTNETWORK_LIBRARY}'),
112 new Library('QtXml', '${QT_QTXML_LIBRARY}'),
113 new Library('QtSvg', '${QT_QTSVG_LIBRARY}'),
114 new Library('X11', '${X11_X11_LIB}'),
115 new Library('z', '${ZLIB_LIBRARY}'),
116 );
117
118 #### Some defaults
119 my $QUILT = "QUILT_PATCHES=debian/patches quilt";
120 my $MSG_PREFIX = "--=--";
121
122 ############### Implementation ###############################
123
124 sub Library::new {
125 my ($cls, $name, $cmake_target, $path) = @_;
126 if (!defined $path) {
127 $path = "/usr/lib/lib$name.so";
128 }
129 return bless( { name => $name, path => $path, cmake_target => $cmake_target }, $cls);
130 }
131
132 sub Library::load {
133 my $self = shift;
134 my @symbols;
135 my @cpp_symbols;
136 if (-r $self->{path}) {
137 open(OBJDUMP, "objdump -T '$self->{path}' |") or die "Unable to run objdump";
138 while(<OBJDUMP>) {
139 # 0000000000021e80 g DF .text 00000000000000f9 Base _XSendClientPrefix
140 if (m/^[0-9a-f]+\s+g\s+DF\s+\.text\s+[0-9a-f]+\s+Base\s+(.*)$/) {
141 my $symbol = $1;
142 if ($symbol =~ m/^_Z/) {
143 # It is C++ symbol. Needs demangling.
144 push @cpp_symbols, $symbol;
145 } else {
146 # FIXME: use hashes?
147 push @symbols, $symbol;
148 }
149 }
150 }
151 close(OBJDUMP);
152 }
153 if (@cpp_symbols) {
154 open2(*OUT, *IN, "c++filt") or die "Unable to run c++filt";
155 for (@cpp_symbols) {
156 print IN "$_\n";
157 }
158 close(IN);
159 my $count = 0;
160 while(<OUT>) {
161 chomp;
162 # Don't care about everything after `('
163 s/\(.*$//;
164 s/^non-virtual thunk to\s+//;
165 push @symbols, $_;
166 $count++;
167 }
168 close(OUT);
169 print(STDERR "Lost a few C++ symbols (", (scalar(@cpp_symbols) - $count),
170 ") while demangling ", $self->to_string(), "\n") unless ($count == scalar(@cpp_symbols));
171 }
172 if (@symbols) {
173 $self->{symbols} = \@symbols;
174 return scalar(@symbols);
175 } else {
176 $self->{symbols} = [];
177 return 0;
178 }
179 }
180
181 sub Library::has_symbol {
182 my ($self, $symbol) = @_;
183
184 for my $sym (@{$self->{symbols}}) {
185 if ($symbol =~ m/^\Q$sym\E/) {
186 return 1;
187 }
188 }
189
190 return 0;
191 }
192
193 sub Library::to_string() {
194 my ($self) = @_;
195 return $self->{name} . " ( " . $self->{path} . " )";
196 }
197
198 sub IgnoreStack::new {
199 return bless( { stack => [] }, shift() );
200 }
201
202 sub IgnoreStack::process_line {
203 my ($self, $line) = @_;
204 my $stack = $self->{stack};
205
206 if ($line =~ m/^\s*((end)?(foreach|function|if|macro|while))\s*[(]/i) {
207 my $isend = defined $2;
208 my $cmd = uc($3);
209
210 if ($isend) {
211 if (@$stack) {
212 my $s = pop @$stack;
213 print STDERR "$MSG_PREFIX There is something wrong with IgnoreStack:\n",
214 "$MSG_PREFIX stack top ($s) does not match end command ($cmd)\n" if $s ne $cmd;
215 } else {
216 print STDERR "$MSG_PREFIX IgnoreStack is empty but got 'end$cmd'. A bug probably.\n";
217 }
218 } else {
219 push @$stack, $cmd;
220 }
221 return 1; # Processed
222 } else {
223 return 0;
224 }
225 }
226
227 sub IgnoreStack::is_empty {
228 return scalar(@{shift()->{stack}}) == 0;
229 }
230
231 sub IgnoreStack::dump_stack {
232 my $self = shift;
233 print "Ignore stack dump:\n";
234 for my $e (@{$self->{stack}}) {
235 print " $e\n";
236 }
237 }
238
239 sub determine_needed_libs {
240 my ($alllibs, $undefrefs) = @_;
241 my @_libs;
242 my @libs = ();
243
244 for my $ref (@$undefrefs) {
245 my $lib;
246 for my $lib (@$alllibs) {
247 if ($lib->has_symbol($ref)) {
248 push @_libs, $lib;
249 next;
250 }
251 }
252 }
253
254 # Kill dupes
255 my $prev = "";
256 for (sort { $a->{cmake_target} cmp $b->{cmake_target} } @_libs) {
257 if ($_ ne $prev) {
258 push @libs, $_;
259 $prev = $_;
260 }
261 }
262
263 return \@libs;
264 }
265
266 sub write_target_link_libs {
267 my ($dir, $target, $libs, $do_backups) = @_;
268 my $cmakelists = File::Spec->catfile($dir, "CMakeLists.txt");
269 my $strlibs = join(" ", map($_->{cmake_target}, @$libs));
270
271 if (-r $cmakelists) {
272 my @contents;
273 my @ignored;
274 my $found = 0;
275 # Ignore directive inside if/endif, while/endwhile etc. blocks
276 my $ignstack = new IgnoreStack;
277
278 # Read and change
279 open(CMAKELISTS, "<$cmakelists");
280 while (<CMAKELISTS>) {
281 if (!$found && !$ignstack->process_line($_) &&
282 m/^\s*(target_link_libraries\s*\(\s*$target\s+)(.*?)(\s*\).*)?$/i) {
283
284 # Fix it
285 my $newline = $1;
286 my $end = $3;
287 $newline .= $2 if ($2);
288 $newline .= " " if ($newline !~ m/\s+$/);
289 $newline .= $strlibs;
290 $newline .= $end if ($end);
291 $newline .= "\n";
292
293 if ($ignstack->is_empty()) {
294 push @contents, $newline;
295 $found = $.;
296 } else {
297 # Save for later use
298 push @ignored, { found => $., newline => $newline };
299 push @contents, $_;
300 }
301 } else {
302 push @contents, $_;
303 }
304 }
305 close(CMAKELISTS);
306
307 if (!$ignstack->is_empty()) {
308 $ignstack->dump_stack();
309 print STDERR "$cmakelists has been processed but ignore stack is not empty. Probably a bug!\n";
310 }
311
312 if (!$found && @ignored == 1) {
313 # That's probably it as there were no other candidates. Replace
314 $found = ${ignored[0]}->{found};
315 my $newline = ${ignored[0]}->{newline};
316 $contents[$found-1] = $newline;
317 } else {
318 print "$MSG_PREFIX More (", scalar(@ignored), ") than 1 target_link_libraries() found in the IF/WHILE etc. blocks\n";
319 }
320
321 if (!$found) {
322 die "$MSG_PREFIX $cmakelists could not be corrected (needed '$strlibs' for target '$target'). Respective target_link_libraries() was not found $MSG_PREFIX";
323 } else {
324 # Write
325 system("$QUILT add '$cmakelists'");
326 open(CMAKELISTS, ">$cmakelists.tmp");
327 for (@contents) {
328 print CMAKELISTS $_;
329 }
330 close(CMAKELISTS);
331 if ($do_backups) {
332 File::Copy::move("$cmakelists", "$cmakelists.orig")
333 or die "Could not rename file '$cmakelists' -> '$cmakelists.org'";
334 }
335 File::Copy::move("$cmakelists.tmp", "$cmakelists")
336 or die "Could not rename file '$cmakelists.tmp' -> '$cmakelists'";
337 print "$MSG_PREFIX $cmakelists edited. Added libraries '$strlibs' for target $target\n";
338 }
339 } else {
340 die "$cmakelists for target $target could not be found. Something is wrong";
341 }
342 }
343
344 sub build_and_fix {
345 my ($sourcedir, $builddir, $buildcmd, $do_backups, $invoke_edit) = @_;
346
347 my $bdir;
348 my $btarget;
349 my $islderror = 0;
350 my @undefrefs;
351 my $link_cmd = 0;
352
353 if (defined $buildcmd) {
354 chdir $builddir;
355 open(MAKE, "$buildcmd 2>&1 |") or die "Unable to run `$buildcmd' in $builddir";
356 } else {
357 chdir $sourcedir;
358 open(MAKE, "debian/rules build 2>&1 |") or die "Unable to run `$buildcmd' in $builddir";
359 }
360
361 while (<MAKE>) {
362 # [ 39%] Building CXX object kwin/kcmkwin/kwindecoration/CMakeFiles
363 print $_;
364 chomp;
365 if ($link_cmd) {
366 #cd /buildd/kdebase-workspace/obj-x86_64-linux-gnu/kwin/kcmkwin/kwindecoration && /usr/bin/cmake -E cmake_link_script CMakeFiles/kcm_kwindecoration.dir/link.txt $MSG_PREFIXverbose=1
367 if (m#^cd (.*) && .*/cmake -E cmake_link_script CMakeFiles/(.*?)\.dir/#) {
368 $btarget = $2;
369 $bdir = File::Spec->abs2rel(Cwd::realpath($1), $builddir);
370 $link_cmd = 0;
371 } else {
372 die "Unrecognized link line";
373 }
374 } elsif (m/\[\s*\d+\%\] Building (.*) object (.*)/) {
375 $bdir = $2;
376 if ($bdir =~ m#CMakeFiles/(.*?)\.dir/#) {
377 $btarget = $1;
378 } else {
379 die "Could not extract target from $bdir";
380 }
381 $bdir =~ s#/CMakeFiles/.*##;
382 } elsif (m/^Linking (.*) (shared (module|library)|executable) /) {
383 $link_cmd = 1;
384 } elsif (m/undefined reference to `(.*)'$/) {
385 # undefined reference to `QDBusMessage::createSignal(QString const&, QString const&, QString const&)'
386 push @undefrefs, $1;
387 } elsif (m/collect\d+: ld returned \d+ exit status/) {
388 $islderror = 1;
389 }
390 }
391
392 close(MAKE);
393
394 chdir $sourcedir;
395
396 # Try to correct the error
397 # print $MSG_PREFIX, $islderror, $bdir, $btarget, @undefrefs;
398 if ($islderror && $bdir && $btarget && @undefrefs) {
399 my $libs = determine_needed_libs(\@LIBS, \@undefrefs);
400 if (@$libs) {
401 write_target_link_libs($bdir, $btarget, $libs, $do_backups);
402 return 0; # again
403 } else {
404 my $cmakelists = "$bdir/CMakeLists.txt";
405 print "$MSG_PREFIX Could not resolve linkage problem automatically. Undefined symbols have not been recognized\n";
406 print "$MSG_PREFIX Target: $btarget; CMakeLists.txt: $cmakelists", "\n";
407 my $quilt_cmd = "$QUILT edit $cmakelists";
408 if ($invoke_edit) {
409 print "$MSG_PREFIX Press any key to edit '$cmakelists' or ^C to cancel ...";
410 <>;
411 system($quilt_cmd);
412 } else {
413 print "$MSG_PREFIX You probably want to run the command to correct the problem yourself: \n",
414 "$MSG_PREFIX \$ $quilt_cmd\n";
415 return 2;
416 }
417 }
418 } else {
419 #print $islderror, ", ", $bdir, ", ", $btarget, ", ", @undefrefs, "\n";
420 print "$MSG_PREFIX Building completed successfully or unrecognized error\n";
421 return 1;
422 }
423 }
424
425 sub load_libraries {
426 my $LIBS = shift;
427
428 print "$MSG_PREFIX Reading dynamic symbol table of ", scalar(@$LIBS), " shared libraries... ", "\n";
429 my $count = 0;
430 for my $lib (@$LIBS) {
431 print "$MSG_PREFIX Loading ", $lib->to_string(), " ... ";
432 if ($lib->load()) {
433 print "success\n";
434 $count++;
435 } else {
436 print "failed (path is not readable or has no symbols)\n";
437 }
438 }
439 return $count;
440 }
441
442 sub check_environment {
443 my ($builddir, $patchname) = @_;
444
445 print "$MSG_PREFIX Script Version v$main::VERSION $MSG_PREFIX", "\n";
446
447 system("dh_testdir") == 0 or die "$MSG_PREFIX Please run this script from the debianized source tree $MSG_PREFIX\n";
448
449 my $toppatch = `$QUILT top`;
450 chomp $toppatch;
451 if (!defined $toppatch || $toppatch ne $patchname) {
452 die "$MSG_PREFIX Quilt top patch must be named '$patchname' when this script is run. Please either:\n" .
453 "$MSG_PREFIX \$ $QUILT push $patchname\n" .
454 "$MSG_PREFIX \$ $QUILT new $patchname\n";
455 }
456 }
457
458 sub get_gnu_build_type {
459 my $buildtype = `dpkg-architecture -qDEB_BUILD_GNU_TYPE`;
460 chomp $buildtype;
461 return $buildtype;
462 }
463
464 ############## Main loop ##############################
465
466 $main::VERSION = "0.3";
467
468 my $sourcedir = Cwd::getcwd();
469 my $builddir = "obj-" . get_gnu_build_type();
470 my $buildcmd = undef;
471 my $patchname = "97_fix_target_link_libraries.diff";
472 my $do_backups = 0;
473 my $show_help = 0;
474 my $invoke_edit = 0;
475
476 if (GetOptions(
477 "help|h|?" => \$show_help,
478 "build-dir|b=s" => \$builddir,
479 "build-command|c=s" => \$buildcmd,
480 "patch-name|p=s" => \$patchname,
481 "do-backups|backup!" => \$do_backups,
482 "invoke-edit|edit|e!" => \$invoke_edit,
483 )) {
484
485 pod2usage(-exitval => 1, -verbose => 2, -noperldoc => 1) if ($show_help);
486
487 $builddir = Cwd::realpath(File::Spec->catdir($sourcedir, $builddir));
488 check_environment($builddir, $patchname);
489
490 if (load_libraries(\@LIBS) == 0) {
491 die "Error: No dynamic symbols found in predefined libraries";
492 }
493
494 my $ret;
495 while (!($ret = build_and_fix($sourcedir, $builddir, $buildcmd, $do_backups, $invoke_edit))) {
496 print "$MSG_PREFIX Linkage problem fixed, rebuilding again\n";
497 }
498
499 if ($ret == 1) {
500 print "$MSG_PREFIX If build process is complete, you may want to run the following command to build/refresh the patch\n" .
501 "$MSG_PREFIX \$ $QUILT refresh --no-index\n";
502 if ($do_backups) {
503 print "$MSG_PREFIX Also run the following command to cleanup backup files:\n" .
504 "$MSG_PREFIX \$ find -name 'CMakeLists.txt.orig' -delete\n";
505 }
506 }
507
508 exit 0;
509 } else {
510 podusage(-exitval => 2, -verbose => 1);
511 }
512

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.5