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

Contents of /scripts/autofixtll

Parent Directory Parent Directory | Revision Log Revision Log


Revision 10334 - (show annotations) (download)
Mon Apr 28 15:42:27 2008 UTC (5 years ago) by modax-guest
File size: 18455 byte(s)
v0.4.1 - enhanced --invoke-edit.
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<-i>, B<--exec-in-build-dir>
64
65 Run build command in the build directory. Default is to run in the source
66 directory.
67
68 =item B<-p>, B<--patch-name>=I<name>
69
70 The name of the quilt patch in which all changes made by the script will be
71 stored. Default is I<97_fix_target_link_libraries.diff>.
72
73 =item B<--do-backups>, B<--backup>
74
75 Whether to backup CMakeLists.txt as CMakeLists.txt.orig before auto-modifying
76 it.
77
78 =item B<--invoke-edit>, B<--edit>, B<-e>
79
80 Invoke `quilt edit' on the respective CMakeLists.txt when undefined references
81 cannot be automatically resolved and restart build process immediately when the
82 editor is closed.
83
84 =back
85
86 =head1 LICENSE
87
88 This program is free software: you can redistribute it and/or modify it under
89 the terms of the GNU General Public License as published by the Free Software
90 Foundation, either version 3 of the License, or (at your option) any later
91 version.
92
93 On B<Debian> systems, the complete text of the GNU GPL v3 can be found in the
94 file F</usr/share/common-licenses/GPL-3>
95
96 =head1 AUTHORS
97
98 Written by Modestas Vainius <modestas@vainius.eu>
99
100 =cut
101
102 use strict;
103 use Cwd qw(getcwd realpath);
104 use File::Spec;
105 use File::Copy;
106 use Getopt::Long;
107 use Pod::Usage;
108 use FileHandle;
109 use IPC::Open2;
110
111 #### Please add predefined libraries to load dynamic symbol from here.
112 # new Library(name, cmake_target, [path]). If path is not specified,
113 # it's /usr/lib/$name.so. Use '' quotes to avoid escaping $.
114 my @LIBS = (
115 new Library('QtDBus', '${QT_QTDBUS_LIBRARY}'),
116 new Library('QtNetwork', '${QT_QTNETWORK_LIBRARY}'),
117 new Library('QtXml', '${QT_QTXML_LIBRARY}'),
118 new Library('QtSvg', '${QT_QTSVG_LIBRARY}'),
119 new Library('QtGui', '${QT_QTGUI_LIBRARY}'),
120 # new Library('pthread', '${CMAKE_THREAD_LIBS_INIT}', '/lib/libpthread.so.0'),
121 new Library('X11', '${X11_X11_LIB}'),
122 new Library('z', '${ZLIB_LIBRARY}'),
123 new Library('solid', '${KDE4_SOLID_LIBS}'),
124 );
125
126 #### Some defaults
127 my $QUILT = "QUILT_PATCHES=debian/patches quilt";
128 my $MSG_PREFIX = "--=--";
129
130 ############### Implementation ###############################
131
132 sub Library::new {
133 my ($cls, $name, $cmake_target, $path) = @_;
134 if (!defined $path) {
135 $path = "/usr/lib/lib$name.so";
136 }
137 return bless( { name => $name, path => $path, cmake_target => $cmake_target }, $cls);
138 }
139
140 sub Library::load {
141 my $self = shift;
142 my @symbols;
143 my %symbhash;
144 my @cpp_symbols;
145 if (-r $self->{path}) {
146 open(OBJDUMP, "objdump -T '$self->{path}' |") or die "Unable to run objdump";
147 while(<OBJDUMP>) {
148 # 0000000000021e80 g DF .text 00000000000000f9 Base _XSendClientPrefix
149 if (m/^[0-9a-f]+\s+g\s+DF\s+\.text\s+[0-9a-f]+\s+Base\s+(.*)$/) {
150 my $symbol = $1;
151 if ($symbol =~ m/^_Z/) {
152 # It is C++ symbol. Needs demangling.
153 push @cpp_symbols, $symbol;
154 } else {
155 push @symbols, $symbol;
156 $symbhash{$symbol} = 1;
157 }
158 }
159 }
160 close(OBJDUMP);
161 }
162 if (@cpp_symbols) {
163 open2(*OUT, *IN, "c++filt") or die "Unable to run c++filt";
164 my $count = 0;
165 for (@cpp_symbols) {
166 print IN "$_\n";
167 $_ = <OUT>;
168 chomp;
169 # Don't care about anything after `('
170 s/\(.*$//;
171 s/^non-virtual thunk to\s+//;
172 push @symbols, $_;
173 $symbhash{$_} = 1;
174 $count++;
175 }
176 close(IN);
177 close(OUT);
178 print(STDERR "Lost a few C++ symbols (", (scalar(@cpp_symbols) - $count),
179 ") while demangling ", $self->to_string(), "\n") unless ($count == scalar(@cpp_symbols));
180 }
181 if (@symbols) {
182 $self->{symbols} = \@symbols;
183 $self->{symbhash} = \%symbhash;
184 return scalar(@symbols);
185 } else {
186 $self->{symbols} = [];
187 $self->{symbhash} = {};
188 return 0;
189 }
190 }
191
192 sub Library::has_symbol {
193 my ($self, $symbol) = @_;
194
195 for my $sym (@{$self->{symbols}}) {
196 if ($symbol =~ m/^\Q$sym\E/) {
197 return 1;
198 }
199 }
200 return 0;
201 }
202
203 sub Library::has_symbol_fast {
204 my ($self, $symbol) = @_;
205
206 # Don't care about anything after `('
207 $symbol =~ s/\(.*$//;
208
209 return (exists $self->{symbhash}{$symbol});
210 }
211
212 sub Library::to_string() {
213 my ($self) = @_;
214 return $self->{name} . " ( " . $self->{path} . " )";
215 }
216
217 sub IgnoreStack::new {
218 return bless( { stack => [] }, shift() );
219 }
220
221 sub IgnoreStack::process_line {
222 my ($self, $line) = @_;
223 my $stack = $self->{stack};
224
225 if ($line =~ m/^\s*((end)?(foreach|function|if|macro|while))\s*[(]/i) {
226 my $isend = defined $2;
227 my $cmd = uc($3);
228
229 if ($isend) {
230 if (@$stack) {
231 my $s = pop @$stack;
232 print STDERR "$MSG_PREFIX There is something wrong with IgnoreStack:\n",
233 "$MSG_PREFIX stack top ($s) does not match end command ($cmd)\n" if $s ne $cmd;
234 } else {
235 print STDERR "$MSG_PREFIX IgnoreStack is empty but got 'end$cmd'. A bug probably.\n";
236 }
237 } else {
238 push @$stack, $cmd;
239 }
240 return 1; # Processed
241 } else {
242 return 0;
243 }
244 }
245
246 sub IgnoreStack::is_empty {
247 return scalar(@{shift()->{stack}}) == 0;
248 }
249
250 sub IgnoreStack::dump_stack {
251 my $self = shift;
252 print "Ignore stack dump:\n";
253 for my $e (@{$self->{stack}}) {
254 print " $e\n";
255 }
256 }
257
258 sub determine_needed_libs {
259 my ($alllibs, $undefrefs) = @_;
260 my @_libs;
261 my @libs = ();
262 my @notfound;
263
264 # Try fast search first
265 for my $ref (@$undefrefs) {
266 my $lib;
267 for my $lib (@$alllibs) {
268 if ($lib->has_symbol_fast($ref)) {
269 push @_libs, $lib;
270 next;
271 }
272 }
273 push @notfound, $ref;
274 }
275
276 # Then try slow one
277 for my $ref (@notfound) {
278 my $lib;
279 for my $lib (@$alllibs) {
280 if ($lib->has_symbol($ref)) {
281 push @_libs, $lib;
282 next;
283 }
284 }
285 }
286
287 # Kill dupes
288 my $prev = "";
289 for (sort { $a->{cmake_target} cmp $b->{cmake_target} } @_libs) {
290 if ($_ ne $prev) {
291 push @libs, $_;
292 $prev = $_;
293 }
294 }
295
296 return \@libs;
297 }
298
299 sub write_target_link_libs {
300 my ($dir, $target, $libs, $do_backups) = @_;
301 my $cmakelists = File::Spec->catfile($dir, "CMakeLists.txt");
302 my $strlibs = join(" ", map($_->{cmake_target}, @$libs));
303
304 if (-r $cmakelists) {
305 my @contents;
306 my @ignored;
307 my $found = 0;
308 # Ignore directive inside if/endif, while/endwhile etc. blocks
309 my $ignstack = new IgnoreStack;
310
311 # Read and change
312 open(CMAKELISTS, "<$cmakelists");
313 while (<CMAKELISTS>) {
314 if (!$found && !$ignstack->process_line($_) &&
315 m/^\s*(target_link_libraries\s*\(\s*$target\s+)(.*?)(\s*\).*)?$/i) {
316
317 # Fix it
318 my $newline = $1;
319 my $end = $3;
320 $newline .= $2 if ($2);
321 $newline .= " " if ($newline !~ m/\s+$/);
322 $newline .= $strlibs;
323 $newline .= $end if ($end);
324 $newline .= "\n";
325
326 if ($ignstack->is_empty()) {
327 push @contents, $newline;
328 $found = $.;
329 } else {
330 # Save for later use
331 push @ignored, { found => $., newline => $newline };
332 push @contents, $_;
333 }
334 } else {
335 push @contents, $_;
336 }
337 }
338 close(CMAKELISTS);
339
340 if (!$ignstack->is_empty()) {
341 $ignstack->dump_stack();
342 print STDERR "$cmakelists has been processed but ignore stack is not empty. Probably a bug!\n";
343 }
344
345 if (!$found && @ignored == 1) {
346 # That's probably it as there were no other candidates. Replace
347 $found = ${ignored[0]}->{found};
348 my $newline = ${ignored[0]}->{newline};
349 $contents[$found-1] = $newline;
350 } elsif (!$found && @ignored > 1) {
351 print "$MSG_PREFIX More (", scalar(@ignored), ") than 1 target_link_libraries() found in the IF/WHILE etc. blocks\n";
352 }
353
354 if (!$found) {
355 print "$MSG_PREFIX $cmakelists could not be corrected (needed '$strlibs' for target '$target'). Respective target_link_libraries() was not found $MSG_PREFIX\n";
356 return 0;
357 } else {
358 # Write
359 system("$QUILT add '$cmakelists'");
360 open(CMAKELISTS, ">$cmakelists.tmp");
361 for (@contents) {
362 print CMAKELISTS $_;
363 }
364 close(CMAKELISTS);
365 if ($do_backups) {
366 File::Copy::move("$cmakelists", "$cmakelists.orig")
367 or die "Could not rename file '$cmakelists' -> '$cmakelists.org'";
368 }
369 File::Copy::move("$cmakelists.tmp", "$cmakelists")
370 or die "Could not rename file '$cmakelists.tmp' -> '$cmakelists'";
371 print "$MSG_PREFIX $cmakelists edited. Added libraries '$strlibs' for target $target\n";
372 return 1;
373 }
374 } else {
375 die "$cmakelists for target $target could not be found. Something is wrong";
376 }
377 }
378
379 sub invoke_edit {
380 my ($cmakelists, $do_invoke) = @_;
381 my $quilt_cmd = "$QUILT edit '$cmakelists'";
382
383 if ($do_invoke) {
384 print "$MSG_PREFIX Press ENTER to edit '$cmakelists' or ^C to cancel ...";
385 <>;
386 return (system($quilt_cmd) == 0) ? 0 : 2;
387 } else {
388 print "$MSG_PREFIX You probably want to run the command to correct the problem yourself: \n",
389 "$MSG_PREFIX \$ $quilt_cmd\n";
390 return 2;
391 }
392 }
393
394 sub build_and_fix {
395 my ($sourcedir, $builddir, $buildcmd, $exec_in_build_dir, $do_backups, $invoke_edit) = @_;
396
397 my $bdir;
398 my $btarget;
399 my $islderror = 0;
400 my @undefrefs;
401 my $link_cmd = 0;
402
403 if ($exec_in_build_dir) {
404 chdir $builddir;
405 } else {
406 chdir $sourcedir;
407 }
408
409 open(MAKE, "$buildcmd 2>&1 |") or die "Unable to run `$buildcmd' in $builddir";
410
411 while (<MAKE>) {
412 # [ 39%] Building CXX object kwin/kcmkwin/kwindecoration/CMakeFiles
413 print $_;
414 chomp;
415 if ($link_cmd) {
416 #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
417 if (m#^cd (.*) && .*/cmake -E cmake_link_script CMakeFiles/(.*?)\.dir/#) {
418 $btarget = $2;
419 $bdir = File::Spec->abs2rel(Cwd::realpath($1), $builddir);
420 $link_cmd = 0;
421 } else {
422 die "Unrecognized link line";
423 }
424 } elsif (m/\[\s*\d+\%\] Building (.*) object (.*)/) {
425 $bdir = $2;
426 if ($bdir =~ m#CMakeFiles/(.*?)\.dir/#) {
427 $btarget = $1;
428 } else {
429 die "Could not extract target from $bdir";
430 }
431 $bdir =~ s#/CMakeFiles/.*##;
432 } elsif (m/^Linking (.*) (shared (module|library)|executable) /) {
433 $link_cmd = 1;
434 } elsif (m/undefined reference to `(.*)'$/) {
435 # undefined reference to `QDBusMessage::createSignal(QString const&, QString const&, QString const&)'
436 push @undefrefs, $1;
437 } elsif (m/collect\d+: ld returned \d+ exit status/) {
438 $islderror = 1;
439 }
440 }
441
442 close(MAKE);
443
444 chdir $sourcedir;
445
446 # Try to correct the error
447 # print $MSG_PREFIX, $islderror, $bdir, $btarget, @undefrefs;
448 if ($islderror && $bdir && $btarget && @undefrefs) {
449 my $libs = determine_needed_libs(\@LIBS, \@undefrefs);
450 if (@$libs) {
451 if (write_target_link_libs($bdir, $btarget, $libs, $do_backups)) {
452 return 0; # again
453 } else {
454 return invoke_edit("$bdir/CMakeLists.txt", $invoke_edit);
455 }
456 } else {
457 my $cmakelists = "$bdir/CMakeLists.txt";
458 print "$MSG_PREFIX Could not resolve linkage problem automatically. Undefined symbols have not been recognized\n";
459 print "$MSG_PREFIX Target: $btarget; CMakeLists.txt: $cmakelists", "\n";
460 return invoke_edit($cmakelists, $invoke_edit);
461 }
462 } else {
463 #print $islderror, ", ", $bdir, ", ", $btarget, ", ", @undefrefs, "\n";
464 print "$MSG_PREFIX Building completed successfully or unrecognized error\n";
465 return 1;
466 }
467 }
468
469 sub load_libraries {
470 my $LIBS = shift;
471
472 print "$MSG_PREFIX Reading dynamic symbol table of ", scalar(@$LIBS), " shared libraries... ", "\n";
473 my $count = 0;
474 for my $lib (@$LIBS) {
475 print "$MSG_PREFIX Loading ", $lib->to_string(), " ... ";
476 if ($lib->load()) {
477 print "success\n";
478 $count++;
479 } else {
480 print "failed (path is not readable or has no symbols)\n";
481 }
482 }
483 return $count;
484 }
485
486 sub check_environment {
487 my ($builddir, $patchname) = @_;
488
489 print "$MSG_PREFIX Script Version v$main::VERSION $MSG_PREFIX", "\n";
490
491 system("dh_testdir") == 0 or die "$MSG_PREFIX Please run this script from the debianized source tree $MSG_PREFIX\n";
492
493 my $toppatch = `$QUILT top`;
494 chomp $toppatch;
495 if (!defined $toppatch || $toppatch ne $patchname) {
496 die "$MSG_PREFIX Quilt top patch must be named '$patchname' when this script is run. Please either:\n" .
497 "$MSG_PREFIX \$ $QUILT push $patchname\n" .
498 "$MSG_PREFIX \$ $QUILT new $patchname\n";
499 }
500 }
501
502 sub get_gnu_build_type {
503 my $buildtype = `dpkg-architecture -qDEB_BUILD_GNU_TYPE`;
504 chomp $buildtype;
505 return $buildtype;
506 }
507
508 ############## Main loop ##############################
509
510 $main::VERSION = "0.4.1";
511
512 my $sourcedir = Cwd::getcwd();
513 my $builddir = "obj-" . get_gnu_build_type();
514 my $exec_in_build_dir = 0;
515 my $buildcmd = "debian/rules build";
516 my $patchname = "97_fix_target_link_libraries.diff";
517 my $do_backups = 0;
518 my $show_help = 0;
519 my $invoke_edit = 0;
520
521 if (GetOptions(
522 "help|h|?" => \$show_help,
523 "build-dir|b=s" => \$builddir,
524 "build-command|c=s" => \$buildcmd,
525 "invoke-edit|edit|e!" => \$invoke_edit,
526 "exec-in-build-dir|i!" => \$exec_in_build_dir,
527 "patch-name|p=s" => \$patchname,
528 "do-backups|backup!" => \$do_backups,
529 )) {
530
531 pod2usage(-exitval => 1, -verbose => 2, -noperldoc => 1) if ($show_help);
532
533 $builddir = Cwd::realpath(File::Spec->catdir($sourcedir, $builddir));
534 check_environment($builddir, $patchname);
535
536 if (load_libraries(\@LIBS) == 0) {
537 die "Error: No dynamic symbols found in predefined libraries";
538 }
539
540 my $ret;
541 while (!($ret = build_and_fix($sourcedir, $builddir, $buildcmd, $exec_in_build_dir, $do_backups, $invoke_edit))) {
542 print "$MSG_PREFIX Linkage problem fixed, rebuilding again\n";
543 }
544
545 if ($ret == 1) {
546 print "$MSG_PREFIX If build process is complete, you may want to run the following command to build/refresh the patch\n" .
547 "$MSG_PREFIX \$ $QUILT refresh --no-index\n";
548 if ($do_backups) {
549 print "$MSG_PREFIX Also run the following command to cleanup backup files:\n" .
550 "$MSG_PREFIX \$ find -name 'CMakeLists.txt.orig' -delete\n";
551 }
552 }
553
554 exit 0;
555 } else {
556 podusage(-exitval => 2, -verbose => 1);
557 }
558

Properties

Name Value
svn:executable *

  ViewVC Help
Powered by ViewVC 1.1.5