1#!/usr/bin/env perl 2############################################################################# 3## 4## Copyright (C) 2016 The Qt Company Ltd. 5## Copyright (C) 2016 Intel Corporation. 6## Contact: https://www.qt.io/licensing/ 7## 8## This file is part of the build configuration tools of the Qt Toolkit. 9## 10## $QT_BEGIN_LICENSE:LGPL$ 11## Commercial License Usage 12## Licensees holding valid commercial Qt licenses may use this file in 13## accordance with the commercial license agreement provided with the 14## Software or, alternatively, in accordance with the terms contained in 15## a written agreement between you and The Qt Company. For licensing terms 16## and conditions see https://www.qt.io/terms-conditions. For further 17## information use the contact form at https://www.qt.io/contact-us. 18## 19## GNU Lesser General Public License Usage 20## Alternatively, this file may be used under the terms of the GNU Lesser 21## General Public License version 3 as published by the Free Software 22## Foundation and appearing in the file LICENSE.LGPL3 included in the 23## packaging of this file. Please review the following information to 24## ensure the GNU Lesser General Public License version 3 requirements 25## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. 26## 27## GNU General Public License Usage 28## Alternatively, this file may be used under the terms of the GNU 29## General Public License version 2.0 or (at your option) the GNU General 30## Public license version 3 or any later version approved by the KDE Free 31## Qt Foundation. The licenses are as published by the Free Software 32## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 33## included in the packaging of this file. Please review the following 34## information to ensure the GNU General Public License requirements will 35## be met: https://www.gnu.org/licenses/gpl-2.0.html and 36## https://www.gnu.org/licenses/gpl-3.0.html. 37## 38## $QT_END_LICENSE$ 39## 40############################################################################# 41 42# 43# Synchronizes Qt header files - internal development tool. 44# 45 46# use packages ------------------------------------------------------- 47use File::Basename; 48use File::Path; 49use File::Spec; 50use Cwd; 51use Cwd 'abs_path'; 52use Config; 53use strict; 54use warnings; 55use English qw(-no_match_vars ); 56 57my $normalizePath_fixDrive = ($^O eq "msys" ? 1 : 0); 58 59###################################################################### 60# Syntax: normalizePath(\$path) 61# Params: Reference to a path that's going to be normalized. 62# 63# Purpose: Converts the path into a form that can be used as include 64# path from C++ sources and qmake's .pro files. 65# Only relevant on Windows. 66# Returns: -none- 67###################################################################### 68sub normalizePath { 69 my $s = shift; 70 $$s =~ s=\\=/=g; 71 if ($normalizePath_fixDrive && ($$s =~ m,^/([a-zA-Z])/(.*), || $$s =~ m,^([a-zA-Z]):/(.*),)) { 72 $$s = lc($1) . ":/$2"; 73 } 74} 75 76# set output basedir to be where ever syncqt is run from 77our $out_basedir = getcwd(); 78normalizePath(\$out_basedir); 79our $build_basedir; 80our $basedir; 81 82# will be defined based on the modules sync.profile 83our (%modules, %moduleheaders, @allmoduleheadersprivate, %classnames, %deprecatedheaders); 84our (@qpa_headers, @private_headers); 85 86# will be derived from sync.profile 87our %reverse_classnames = (); 88my %ignore_for_include_check = (); 89my %ignore_for_qt_begin_namespace_check = (); 90 91# global variables (modified by options) 92my $isunix = 0; 93my $module = 0; 94my $showonly = 0; 95my $verbose_level = 1; 96my $remove_stale = 1; 97my $force_win = 0; 98my $check_includes = 0; 99my $copy_headers = 0; 100my $minimal = 0; 101my $module_version = 0; 102my @modules_to_sync ; 103 104 105# functions ---------------------------------------------------------- 106 107###################################################################### 108# Syntax: showUsage() 109# Params: -none- 110# 111# Purpose: Show the usage of the script. 112# Returns: -none- 113###################################################################### 114sub showUsage 115{ 116 print "$0 usage:\n"; 117 print " <module directory> Specifies which module to sync header files for (required for shadow builds!)\n\n"; 118 119 print " -copy Copy headers instead of include-fwd(default: " . ($copy_headers ? "yes" : "no") . ")\n"; 120 print " -remove-stale Removes stale headers (default: " . ($remove_stale ? "yes" : "no") . ")\n"; 121 print " -windows Force platform to Windows (default: " . ($force_win ? "yes" : "no") . ")\n"; 122 print " -showonly Show action but not perform (default: " . ($showonly ? "yes" : "no") . ")\n"; 123 print " -minimal Do not create CamelCase headers (default: " . ($minimal ? "yes" : "no") . ")\n"; 124 print " -outdir <PATH> Specify output directory for sync (default: $out_basedir)\n"; 125 print " -builddir <PATH> Specify build directory for sync (default: same as -outdir)\n"; 126 print " -version <VERSION> Specify the module's version (default: detect from qglobal.h)\n"; 127 print " -quiet Only report problems, not activity (same as -verbose 0)\n"; 128 print " -v, -verbose <level> Sets the verbosity level (max. 4) (default: $verbose_level)\n"; 129 print " The short form increases the level by +1\n"; 130 print " -separate-module <NAME>:<PROFILEDIR>:<HEADERDIR>\n"; 131 print " Create headers for <NAME> with original headers in\n"; 132 print " <HEADERDIR> relative to <PROFILEDIR> \n"; 133 print " -help This help\n"; 134 exit 0; 135} 136 137###################################################################### 138# Syntax: checkUnix() 139# Params: -none- 140# 141# Purpose: Check if script runs on a Unix system or not. Cygwin 142# systems are _not_ detected as Unix systems. 143# Returns: 1 if a unix system, else 0. 144###################################################################### 145sub checkUnix { 146 my ($r) = 0; 147 if ( $force_win != 0) { 148 return 0; 149 } elsif ( -f "/bin/uname" ) { 150 $r = 1; 151 (-f "\\bin\\uname") && ($r = 0); 152 } elsif ( -f "/usr/bin/uname" ) { 153 $r = 1; 154 (-f "\\usr\\bin\\uname") && ($r = 0); 155 } 156 if($r) { 157 $_ = $Config{'osname'}; 158 $r = 0 if( /(ms)|(cyg)win/i ); 159 } 160 return $r; 161} 162 163sub checkRelative { 164 my ($dir) = @_; 165 return 0 if($dir =~ /^\//); 166 return 0 if(!checkUnix() && $dir =~ /[a-zA-Z]:[\/\\]/); 167 return 1; 168} 169 170###################################################################### 171# Syntax: shouldMasterInclude(iheader) 172# Params: iheader, string, filename to verify inclusion 173# 174# Purpose: Determines if header should be in the master include file. 175# Returns: 0 if file contains "#pragma qt_no_master_include" or not 176# able to open, else 1. 177###################################################################### 178sub shouldMasterInclude { 179 my ($iheader) = @_; 180 return 0 if (basename($iheader) =~ /_/); 181 return 0 if (basename($iheader) =~ /qconfig/); 182 local $/ = "\x0a"; 183 if (open(F, "<$iheader")) { 184 while (<F>) { 185 s/\x0d?\x0a//; 186 return 0 if (/^\#pragma qt_no_master_include$/); 187 } 188 close(F); 189 } else { 190 return 0; 191 } 192 return 1; 193} 194 195###################################################################### 196# Syntax: classNames(iheader, clean, requires) 197# Params: iheader, string, filename to parse for classname "symlinks" 198# (out) clean, boolean, will be set to false if the header isn't clean 199# (out) requires, string, will be set to non-empty if the header 200# requires a feature 201# 202# Purpose: Scans through iheader to find all classnames that should be 203# synced into library's include structure. 204# Returns: List of all class names in a file. 205###################################################################### 206sub classNames { 207 my @ret; 208 my ($iheader, $clean, $requires) = @_; 209 $$clean = 1; 210 $$requires = ""; 211 212 my $suspended = 0; 213 my $ihdrbase = basename($iheader); 214 215 my $parsable = ""; 216 local $/ = "\x0a"; 217 if(open(F, "<$iheader")) { 218 while(<F>) { 219 s/\x0d?\x0a//; 220 my $line = $_; 221 if($line =~ /^\#/) { 222 $$clean = 0 if ($line =~ m/^#pragma qt_sync_skip_header_check/); 223 return @ret if($line =~ m/^#pragma qt_sync_stop_processing/); 224 push(@ret, $1) if($line =~ m/^#pragma qt_class\(([^)]*)\)[\r\n]*$/); 225 $suspended = 1 if ($line =~ m/^#pragma qt_sync_suspend_processing/); 226 $suspended = 0 if ($line =~ m/^#pragma qt_sync_resume_processing/); 227 $line = 0; 228 } 229 if ($line && !$suspended) { 230 $line =~ s,//.*$,,; #remove c++ comments 231 $line .= ";" if($line =~ m/^Q_[A-Z_0-9]*\(.*\)[\r\n]*$/); #qt macro 232 $line .= ";" if($line =~ m/^QT_(BEGIN|END)_HEADER[\r\n]*$/); #qt macro 233 $line .= ";" if($line =~ m/^QT_(BEGIN|END)_NAMESPACE(_[A-Z]+)*[\r\n]*$/); #qt macro 234 $line .= ";" if($line =~ m/^QT_MODULE\(.*\)[\r\n]*$/); # QT_MODULE macro 235 $line .= ";" if($line =~ m/^QT_WARNING_(PUSH|POP|DISABLE_\w+\(.*\))[\r\n]*$/); # qt macros 236 $$requires = $1 if ($line =~ m/^QT_REQUIRE_CONFIG\((.*)\);[\r\n]*$/); 237 $parsable .= " " . $line; 238 } 239 } 240 close(F); 241 } 242 243 my $last_definition = 0; 244 my @namespaces; 245 for(my $i = 0; $i < length($parsable); $i++) { 246 my $definition = 0; 247 my $character = substr($parsable, $i, 1); 248 if($character eq "/" && substr($parsable, $i+1, 1) eq "*") { #I parse like this for greedy reasons 249 for($i+=2; $i < length($parsable); $i++) { 250 my $end = substr($parsable, $i, 2); 251 if($end eq "*/") { 252 $last_definition = $i+2; 253 $i++; 254 last; 255 } 256 } 257 } elsif($character eq "{") { 258 my $brace_depth = 1; 259 my $block_start = $i + 1; 260 BLOCK: for($i+=1; $i < length($parsable); $i++) { 261 my $ignore = substr($parsable, $i, 1); 262 if($ignore eq "{") { 263 $brace_depth++; 264 } elsif($ignore eq "}") { 265 $brace_depth--; 266 unless($brace_depth) { 267 for(my $i2 = $i+1; $i2 < length($parsable); $i2++) { 268 my $end = substr($parsable, $i2, 1); 269 if($end eq ";" || $end ne " ") { 270 $definition = substr($parsable, $last_definition, $block_start - $last_definition) . "}"; 271 $i = $i2 if($end eq ";"); 272 $last_definition = $i + 1; 273 last BLOCK; 274 } 275 } 276 } 277 } 278 } 279 } elsif($character eq ";") { 280 $definition = substr($parsable, $last_definition, $i - $last_definition + 1); 281 $last_definition = $i + 1; 282 } elsif($character eq "}") { 283 # a naked } must be a namespace ending 284 # if it's not a namespace, it's eaten by the loop above 285 pop @namespaces; 286 $last_definition = $i + 1; 287 } 288 289 if (substr($parsable, $last_definition, $i - $last_definition + 1) =~ m/ namespace ([^ ]*) / 290 && substr($parsable, $i+1, 1) eq "{") { 291 push @namespaces, $1; 292 293 # Eat the opening { so that the condensing loop above doesn't see it 294 $i++; 295 $last_definition = $i + 1; 296 } 297 298 if($definition) { 299 $definition =~ s=[\n\r]==g; 300 $definition =~ s/QT_DEPRECATED_X\s*\(\s*".*?"\s*\)//g; 301 my @symbols; 302 my $post_kw = qr/Q_DECL_FINAL|final|sealed/; # add here macros and keywords that go after the class-name of a class definition 303 if($definition =~ m/^ *typedef *.*\(\*([^\)]*)\)\(.*\);$/) { 304 push @symbols, $1; 305 } elsif($definition =~ m/^ *typedef +(.*) +([^ ]*);$/) { 306 push @symbols, $2; 307 } elsif($definition =~ m/^ *(template *<.*> *)?(class|struct) +([^ <>]* +)?((?!$post_kw)[^<\s]+) ?(<[^>]*> ?)?\s*(?:$post_kw)?\s*((,|:)\s*(public|protected|private) *.*)? *\{\}$/o) { 308 push @symbols, $4; 309 } elsif($definition =~ m/^ *Q_DECLARE_.*ITERATOR\((.*)\);$/) { 310 push @symbols, "Q" . $1 . "Iterator"; 311 push @symbols, "QMutable" . $1 . "Iterator"; 312 } 313 314 our $publicclassregexp; 315 foreach my $symbol (@symbols) { 316 $symbol = (join("::", @namespaces) . "::" . $symbol) if (scalar @namespaces); 317 318 my $revhdr = $reverse_classnames{$symbol}; 319 next if (defined($revhdr) and $revhdr ne $ihdrbase); 320 if ($symbol =~ /^Q[^:]*$/) { # no-namespace, starting with Q 321 push @ret, $symbol; 322 } elsif (defined($publicclassregexp)) { 323 push @ret, $symbol if ($symbol =~ $publicclassregexp); 324 } 325 } 326 } 327 } 328 return @ret; 329} 330 331sub check_header { 332 my ($lib, $header, $iheader, $public_header, $private_header) = @_; 333 my $header_skip_qt_begin_namespace_test = 0; 334 335 return if ($ignore_for_include_check{$header}); 336 if ($public_header) { 337 $header_skip_qt_begin_namespace_test = 1 if ($ignore_for_qt_begin_namespace_check{$header}); 338 } 339 340 local $/ = "\x0a"; 341 open(F, "<$iheader") or return; 342 my $qt_begin_namespace_found = 0; 343 my $qt_end_namespace_found = 0; 344 my $qt_namespace_suffix = ""; 345 my $line; 346 my $stop_processing = 0; 347 my $we_mean_it = 0; 348 while ($line = <F>) { 349 $line =~ s/\x0d?\x0a//; 350 my $output_line = 1; 351 if ($line =~ /^ *\# *pragma (qt_no_included_check|qt_sync_stop_processing)/) { 352 $stop_processing = 1; 353 last; 354 } 355 if ($line =~ /^ *\# *include/) { 356 my $include = $line; 357 if ($line =~ /<.*>/) { 358 $include =~ s,.*<(.*)>.*,$1,; 359 } elsif ($line =~ /".*"/) { 360 $include =~ s,.*"(.*)".*,$1,; 361 } else { 362 $include = 0; 363 } 364 if ($include && $public_header) { 365 print STDERR "$lib: ERROR: $iheader includes private header $include\n" if ($include =~ /_p\.h$/); 366 for my $trylib (keys(%modules)) { 367 if (-e "$out_basedir/include/$trylib/$include") { 368 print STDERR "$lib: WARNING: $iheader includes $include when it should include $trylib/$include\n"; 369 } 370 } 371 } 372 } elsif (!$private_header) { 373 if ($header_skip_qt_begin_namespace_test == 0 and $line =~ /^QT_BEGIN_NAMESPACE(_[A-Z_]+)?\s*$/) { 374 $qt_namespace_suffix = defined($1) ? $1 : ""; 375 $qt_begin_namespace_found = 1; 376 } elsif ($header_skip_qt_begin_namespace_test == 0 and $line =~ /^QT_END_NAMESPACE$qt_namespace_suffix\s*$/) { 377 $qt_end_namespace_found = 1; 378 } 379 } elsif ($line =~ "^// We mean it.") { 380 ++$we_mean_it; 381 } 382 } 383 384 if ($public_header) { 385 if ($header_skip_qt_begin_namespace_test == 0 and $stop_processing == 0) { 386 if ($qt_begin_namespace_found == 0) { 387 print STDERR "$lib: WARNING: $iheader does not include QT_BEGIN_NAMESPACE\n"; 388 } 389 390 if ($qt_begin_namespace_found && $qt_end_namespace_found == 0) { 391 print STDERR "$lib: WARNING: $iheader has QT_BEGIN_NAMESPACE$qt_namespace_suffix but no QT_END_NAMESPACE$qt_namespace_suffix\n"; 392 } 393 } 394 } elsif ($private_header) { 395 print STDERR "$lib: WARNING: $iheader does not have the \"We mean it.\" warning\n" if (!$we_mean_it); 396 } 397 398 close(F); 399} 400 401sub make_path { 402 my ($dir, $lib, $be_verbose) = @_; 403 unless(-e $dir) { 404 mkpath $dir; 405 $dir = "<outbase>" . substr($dir, length($out_basedir)) if ($be_verbose < 3); 406 print "$lib: mkpath $dir\n" if ($be_verbose > 1); 407 } 408} 409 410###################################################################### 411# Syntax: syncHeader(header, iheader, copy, timestamp) 412# Params: header, string, filename to create "symlink" for 413# iheader, string, destination name of symlink 414# copy, forces header to be a copy of iheader 415# timestamp, the requested modification time if copying 416# 417# Purpose: Syncronizes header to iheader 418# Returns: 1 if successful, else 0. 419###################################################################### 420sub syncHeader { 421 my ($lib, $header, $iheader, $copy, $ts) = @_; 422 normalizePath(\$iheader); 423 normalizePath(\$header); 424 return copyFile($lib, $iheader, $header) if($copy); 425 426 unless(-e $header) { 427 my $header_dir = dirname($header); 428 make_path($header_dir, $lib, $verbose_level); 429 430 #write it 431 my $iheader_out = fixPaths($iheader, $header_dir); 432 open(HEADER, ">$header") || die "Could not open $header for writing: $!\n"; 433 print HEADER "#include \"$iheader_out\"\n"; 434 close HEADER; 435 if(defined($ts)) { 436 utime(time, $ts, $header) or die "$iheader, $header"; 437 } 438 return 1; 439 } 440 return 0; 441} 442 443###################################################################### 444# Syntax: fixPaths(file, dir) 445# Params: file, string, filepath to be made relative to dir 446# dir, string, dirpath for point of origin 447# 448# Purpose: file is made relative (if possible) of dir. 449# Returns: String with the above applied conversion. 450###################################################################### 451 452sub cleanupPath { 453 my ($file) = @_; 454 normalizePath(\$file); 455 while ($file =~ s,/[^/]+/\.\./,/,) {} 456 return $file; 457} 458 459sub fixPaths { 460 my ($file, $dir) = @_; 461 462 my $out = File::Spec->abs2rel(cleanupPath($file), cleanupPath($dir)); 463 $out =~ s,\\,/,g; 464 $out = "\"$out\"" if ($out =~ / /); 465 return $out; 466} 467 468###################################################################### 469# Syntax: fileContents(filename) 470# Params: filename, string, filename of file to return contents 471# 472# Purpose: Get the contents of a file. 473# Returns: String with contents of the file, or empty string if file 474# doens't exist. 475# Warning: Dies if it does exist but script cannot get read access. 476###################################################################### 477sub fileContents { 478 my ($filename) = @_; 479 my $filecontents = ""; 480 if (-e $filename) { 481 open(I, "< $filename") || die "Could not open $filename for reading, read block?"; 482 local $/; 483 binmode I; 484 $filecontents = <I>; 485 close I; 486 } 487 return $filecontents; 488} 489 490###################################################################### 491# Syntax: writeFile(filename, contents) 492# Params: filename, string, filename of file to write 493# contents, string, new contents for the file 494# 495# Purpose: Write file with given contents. If new contents match old 496# ones, do no change the file's timestamp. 497# Returns: 1 if the file's contents changed. 498###################################################################### 499sub writeFile { 500 my ($filename, $contents, $lib, $what) = @_; 501 my $oldcontents = fileContents($filename); 502 $oldcontents =~ s/\r//g; # remove \r's , so comparison is ok on all platforms 503 if ($oldcontents ne $contents) { 504 open(O, "> " . $filename) || die "Could not open $filename for writing: $!\n"; 505 print O $contents; 506 close O; 507 if ($lib && $verbose_level) { 508 my $action = ($oldcontents eq "") ? "created" : "updated"; 509 print "$lib: $action $what\n"; 510 } 511 return 1; 512 } 513 return 0; 514} 515 516###################################################################### 517# Syntax: fileCompare(file1, file2) 518# Params: file1, string, filename of first file 519# file2, string, filename of second file 520# 521# Purpose: Determines if files are equal, and which one is newer. 522# Returns: 0 if files are equal no matter the timestamp, -1 if file1 523# is newer, 1 if file2 is newer. 524###################################################################### 525sub fileCompare { 526 my ($file1, $file2) = @_; 527 my $file1contents = fileContents($file1); 528 my $file2contents = fileContents($file2); 529 if (! -e $file1) { return 1; } 530 if (! -e $file2) { return -1; } 531 return $file1contents ne $file2contents ? (stat($file2))[9] <=> (stat($file1))[9] : 0; 532} 533 534###################################################################### 535# Syntax: copyFile(file, ifile) 536# Params: file, string, filename to create duplicate for 537# ifile, string, destination name of duplicate 538# 539# Purpose: Keeps files in sync so changes in the newer file will be 540# written to the other. 541# Returns: 1 if files were synced, else 0. 542# Warning: Dies if script cannot get write access. 543###################################################################### 544sub copyFile 545{ 546 my ($lib, $file,$ifile, $copy,$knowdiff,$filecontents,$ifilecontents) = @_; 547 # Bi-directional synchronization 548 open( I, "< " . $file ) || die "Could not open $file for reading"; 549 local $/; 550 binmode I; 551 $filecontents = <I>; 552 close I; 553 if ( open(I, "< " . $ifile) ) { 554 local $/; 555 binmode I; 556 $ifilecontents = <I>; 557 close I; 558 $copy = fileCompare($file, $ifile); 559 $knowdiff = 0, 560 } else { 561 $copy = -1; 562 $knowdiff = 1; 563 } 564 565 if ( $knowdiff || ($filecontents ne $ifilecontents) ) { 566 if ( $copy > 0 ) { 567 my $file_dir = dirname($file); 568 make_path($file_dir, $lib, $verbose_level); 569 open(O, "> " . $file) || die "Could not open $file for writing (no write permission?)"; 570 local $/; 571 binmode O; 572 print O $ifilecontents; 573 close O; 574 utime time, (stat($ifile))[9], $file; 575 return 1; 576 } elsif ( $copy < 0 ) { 577 my $ifile_dir = dirname($ifile); 578 make_path($ifile_dir, $lib, $verbose_level); 579 open(O, "> " . $ifile) || die "Could not open $ifile for writing (no write permission?)"; 580 local $/; 581 binmode O; 582 print O $filecontents; 583 close O; 584 utime time, (stat($file))[9], $ifile; 585 return 1; 586 } 587 } 588 return 0; 589} 590 591###################################################################### 592# Syntax: findFiles(dir, match) 593# Params: dir, string, directory to search for name 594# match, string, regular expression to match in dir 595# 596# Purpose: Finds files matching a regular expression. 597# Returns: List of matching files. 598# 599# Example: 600# findFiles("/tmp", "^#") - finds #* files in /tmp 601###################################################################### 602sub findFiles { 603 my ($dir, $match) = @_; 604 my ($file,$p,@files); 605 local(*D); 606 normalizePath(\$dir); 607 ($dir eq "") && ($dir = "."); 608 if ( opendir(D,$dir) ) { 609 if ( $dir eq "." ) { 610 $dir = ""; 611 } else { 612 ($dir =~ /\/$/) || ($dir .= "/"); 613 } 614 foreach $file ( sort readdir(D) ) { 615 next if ( $file =~ /^\.\.?$/ ); 616 $p = $file; 617 ($file =~ /$match/) && (push @files, $p); 618 } 619 closedir(D); 620 } 621 return @files; 622} 623 624sub listSubdirs { 625 my @subdirs = @_; 626 foreach my $subdir (@subdirs) { 627 opendir DIR, $subdir or die "Huh, directory ".$subdir." cannot be opened."; 628 foreach my $t (sort readdir(DIR)) { 629 push @subdirs, "$subdir/$t" if(-d "$subdir/$t" && !($t eq ".") && 630 !($t eq "..") && !($t eq ".obj") && 631 !($t eq ".moc") && !($t eq ".rcc") && 632 !($t eq ".uic") && !($t eq "build") && 633 !($t eq "doc")); 634 } 635 closedir DIR; 636 } 637 return @subdirs; 638} 639 640###################################################################### 641# Syntax: loadSyncProfile() 642# 643# Purpose: Loads the sync.profile. 644###################################################################### 645sub loadSyncProfile { 646 if ($verbose_level) { 647 print("<srcbase> = $basedir \n"); 648 print("<bldbase> = $build_basedir \n"); 649 print("<outbase> = $out_basedir \n"); 650 } 651 652 my $syncprofile = "$basedir/sync.profile"; 653 my $result; 654 unless ($result = do "$syncprofile") { 655 die "syncqt couldn't parse $syncprofile: $@" if $@; 656 die "syncqt couldn't execute $syncprofile: $!" unless defined $result; 657 } 658 659 for my $fn (keys %classnames) { 660 for my $cn (split(/,/, $classnames{$fn})) { 661 $reverse_classnames{$cn} = $fn; 662 } 663 } 664 665 push @private_headers, qr/_p(ch)?\.h$/; 666} 667 668sub basePrettify { 669 my ($arg) = @_; 670 $$arg =~ s,^\Q$basedir\E,<srcbase>,; 671 $$arg =~ s,^\Q$build_basedir\E,<bldbase>,; 672 $$arg =~ s,^\Q$out_basedir\E,<outbase>,; 673} 674 675sub cleanPath { 676 my ($arg) = @_; 677 while ($arg =~ s,[^/]+/\.\.(/|$),,) {} 678 return $arg; 679} 680 681###################################################################### 682# Syntax: locateSyncProfile() 683# 684# Purpose: Locates the sync.profile. 685###################################################################### 686sub locateSyncProfile 687{ 688 my ($directory) = @_; 689 $directory = abs_path($directory); 690 while (1) { 691 my $file = $directory."/sync.profile"; 692 return $file if (-e $file); 693 my $odir = $directory; 694 $directory = dirname($directory); 695 return undef if ($directory eq $odir); 696 } 697} 698 699sub isQpaHeader 700{ 701 my ($header) = @_; 702 foreach my $qpa_header (@qpa_headers) { 703 return 1 if ($header =~ $qpa_header); 704 } 705 return 0; 706} 707 708sub isPrivateHeader 709{ 710 my ($header) = @_; 711 foreach my $private_header (@private_headers) { 712 return 1 if ($header =~ $private_header); 713 } 714 return 0; 715} 716 717sub globosort($$) { 718 my ($a, $b) = @_; 719 if ($a =~ /^q(.*)global\.h$/) { 720 my $sa = $1; 721 if ($b =~ /^q(.*)global\.h$/) { 722 my $sb = $1; 723 # Compare stems so qglobal.h (empty stem) is first: 724 return $sa cmp $sb; 725 } 726 return -1; # $a is global, so before $b 727 } 728 return +1 if $b =~ /^q.*global\.h$/; # $a not global, so after $b 729 return $a cmp $b; 730} 731 732# check if this is an in-source build, and if so use that as the basedir too 733$basedir = locateSyncProfile($out_basedir); 734if ($basedir) { 735 $basedir = dirname($basedir) ; 736 normalizePath(\$basedir); 737} 738 739# -------------------------------------------------------------------- 740# "main" function 741# -------------------------------------------------------------------- 742 743while ( @ARGV ) { 744 my $var = 0; 745 my $val = 0; 746 747 #parse 748 my $arg = shift @ARGV; 749 if ($arg eq "-h" || $arg eq "-help" || $arg eq "-?" || $arg eq "?") { 750 $var = "show_help"; 751 $val = "yes"; 752 } elsif($arg eq "-copy") { 753 $var = "copy"; 754 $val = "yes"; 755 } elsif($arg eq "-o" || $arg eq "-outdir") { 756 $var = "output"; 757 $val = shift @ARGV; 758 } elsif($arg eq "-builddir") { 759 $var = "build"; 760 $val = shift @ARGV; 761 } elsif($arg eq "-showonly" || $arg eq "-remove-stale" || $arg eq "-windows" || 762 $arg eq "-relative" || $arg eq "-check-includes") { 763 $var = substr($arg, 1); 764 $val = "yes"; 765 } elsif($arg =~ /^-no-(.*)$/) { 766 $var = $1; 767 $val = "no"; 768 #these are for commandline compat 769 } elsif($arg eq "-inc") { 770 $var = "output"; 771 $val = shift @ARGV; 772 } elsif($arg eq "-module") { 773 $var = "module"; 774 $val = shift @ARGV; 775 } elsif($arg eq "-separate-module") { 776 $var = "separate-module"; 777 $val = shift @ARGV; 778 } elsif($arg eq "-show") { 779 $var = "showonly"; 780 $val = "yes"; 781 } elsif($arg eq "-quiet") { 782 $var = "verbose"; 783 $val = "0"; 784 } elsif($arg eq "-v") { 785 $var = "verbose"; 786 $val = "yes"; 787 } elsif($arg eq "-verbose") { 788 $var = "verbose"; 789 $val = shift @ARGV; 790 } elsif($arg eq "-minimal") { 791 $var = "minimal"; 792 $val = "yes"; 793 } elsif($arg eq "-version") { 794 $var = "version"; 795 $val = shift @ARGV; 796 } elsif($arg =~/^-/) { 797 print STDERR "ERROR: Unknown option: $arg\n\n" if (!$var); 798 showUsage(); 799 } else { 800 $basedir = locateSyncProfile($arg); 801 die "Could not find a sync.profile for '$arg'\n" if (!$basedir); 802 $basedir = dirname($basedir); 803 normalizePath(\$basedir); 804 $var = "ignore"; 805 } 806 807 #do something 808 if(!$var || $var eq "show_help") { 809 print STDERR "ERROR: Unknown option: $arg\n\n" if (!$var); 810 showUsage(); 811 } elsif ($var eq "copy") { 812 if($val eq "yes") { 813 $copy_headers++; 814 } elsif($showonly) { 815 $copy_headers--; 816 } 817 } elsif ($var eq "showonly") { 818 if($val eq "yes") { 819 $showonly++; 820 } elsif($showonly) { 821 $showonly--; 822 } 823 } elsif ($var eq "verbose") { 824 if($val eq "yes") { 825 $verbose_level++; 826 } elsif($val eq "no" && $verbose_level) { 827 $verbose_level--; 828 } else { 829 $verbose_level = int($val); 830 } 831 } elsif ($var eq "check-includes") { 832 if($val eq "yes") { 833 $check_includes++; 834 } elsif($check_includes) { 835 $check_includes--; 836 } 837 } elsif ($var eq "remove-stale") { 838 if($val eq "yes") { 839 $remove_stale++; 840 } elsif($remove_stale) { 841 $remove_stale--; 842 } 843 } elsif ($var eq "windows") { 844 if($val eq "yes") { 845 $force_win++; 846 } elsif($force_win) { 847 $force_win--; 848 } 849 } elsif ($var eq "minimal") { 850 if($val eq "yes") { 851 $minimal++; 852 } elsif($minimal) { 853 $minimal--; 854 } 855 } elsif ($var eq "module") { 856 push @modules_to_sync, $val; 857 } elsif ($var eq "separate-module") { 858 my ($module, $prodir, $headerdir) = split(/:/, $val); 859 $modules{$module} = $prodir; 860 push @modules_to_sync, $module; 861 $moduleheaders{$module} = $headerdir; 862 } elsif ($var eq "version") { 863 if($val) { 864 $module_version = $val; 865 } else { 866 die "The -version option requires an argument"; 867 } 868 } elsif ($var eq "output") { 869 my $outdir = $val; 870 if(checkRelative($outdir)) { 871 $out_basedir = getcwd(); 872 chomp $out_basedir; 873 $out_basedir .= "/" . $outdir; 874 } else { 875 $out_basedir = $outdir; 876 } 877 normalizePath(\$out_basedir); 878 } elsif ($var eq "build") { 879 my $outdir = $val; 880 if (checkRelative($outdir)) { 881 $build_basedir = getcwd(); 882 chomp $build_basedir; 883 $build_basedir .= "/" . $outdir; 884 } else { 885 $build_basedir = $outdir; 886 } 887 normalizePath(\$build_basedir); 888 } 889} 890 891# if we have no $basedir we cannot be sure which sources you want, so die 892die "Could not find any sync.profile for your module!\nPass <module directory> to syncqt to sync your header files.\nsyncqt failed" if (!$basedir); 893die "The -version argument is mandatory" if (!$module_version); 894 895$build_basedir = $out_basedir if (!defined($build_basedir)); 896 897our @ignore_headers = (); 898our @ignore_for_include_check = (); 899our @ignore_for_qt_begin_namespace_check = (); 900our @ignore_for_qt_module_check = (); 901our %inject_headers = (); 902 903# load the module's sync.profile here, before we can 904loadSyncProfile(); 905 906@modules_to_sync = keys(%modules) if($#modules_to_sync == -1); 907 908for my $p (keys %inject_headers) { 909 push @ignore_for_include_check, @{$inject_headers{$p}}; 910} 911 912my %allmoduleheadersprivate = map { $_ => 1 } @allmoduleheadersprivate; 913%ignore_for_include_check = map { $_ => 1 } @ignore_for_include_check; 914%ignore_for_qt_begin_namespace_check = map { $_ => 1 } @ignore_for_qt_begin_namespace_check; 915 916$isunix = checkUnix; #cache checkUnix 917 918foreach my $lib (@modules_to_sync) { 919 die "No such module: $lib" unless(defined $modules{$lib}); 920 921 #iteration info 922 my $module = $modules{$lib}; 923 my $is_qt = !($module =~ s/^!//); 924 my @dirs = split(/;/, $module); 925 my $dir = $dirs[0]; 926 shift @dirs if ($dir =~ s/^>//); 927 928 my $pathtoheaders = ""; 929 $pathtoheaders = $moduleheaders{$lib} if ($moduleheaders{$lib}); 930 931 my $allheadersprivate = 0; 932 $allheadersprivate = 1 if $allmoduleheadersprivate{$lib}; 933 934 #information used after the syncing 935 my $pri_install_gfiles = ""; 936 my $pri_install_files = ""; 937 my $pri_install_pfiles = ""; 938 my $pri_install_qpafiles = ""; 939 my $pri_injections = ""; 940 my $pri_clean_files = ""; 941 942 my $libcapitals = uc($lib); 943 my %master_contents = (); 944 945 #remove the old files 946 if ($remove_stale && !$minimal) { 947 my %injections = (); 948 for my $p (keys %inject_headers) { 949 next unless ($p =~ /^\Q$dir\E(\/|$)/); 950 my $sp = $p; 951 $sp =~ s,^\Q$basedir\E/,$build_basedir/,; 952 for my $n (@{$inject_headers{$p}}) { 953 $injections{$sp."/".$n} = 1; 954 } 955 } 956 my @subdirs = ("$out_basedir/include/$lib"); 957 foreach my $subdir (@subdirs) { 958 if (opendir DIR, $subdir) { 959 foreach my $t (sort { $b cmp $a } readdir(DIR)) { 960 next if ($t =~ /\.pri$/); 961 next if ($t =~ /^qt[a-z0-9]+-config(_p)?\.h$/); 962 my $file = "$subdir/$t"; 963 if(-d $file) { 964 push @subdirs, $file unless($t eq "." || $t eq ".."); 965 } else { 966 my @files = ($file); 967 #push @files, "$out_basedir/include/Qt/$t" if(-e "$out_basedir/include/Qt/$t"); 968 foreach my $file (@files) { 969 my $remove_file = 0; 970 local $/ = "\x0a"; 971 if(open(F, "<$file")) { 972 while(my $line = <F>) { 973 $line =~ s/\x0d?\x0a//; 974 if($line =~ /^\#include \"([^\"]*)\"$/) { 975 my $include = $1; 976 $include = $subdir . "/" . $include unless(substr($include, 0, 1) eq "/"); 977 $remove_file = 1 unless(-e $include or defined $injections{cleanPath($include)}); 978 } else { 979 $remove_file = 0; 980 last; 981 } 982 } 983 close(F); 984 unlink $file if($remove_file); 985 } 986 } 987 } 988 } 989 closedir DIR; 990 } 991 992 } 993 } 994 995 #create the new ones 996 foreach my $current_dir (@dirs) { 997 my $thisprivate = 0; 998 ($current_dir =~ s/^\^//) and $thisprivate = 1; 999 my @headers_paths = split(/;/, $pathtoheaders); 1000 if (@headers_paths) { 1001 @headers_paths = map { "$current_dir/$_" } @headers_paths; 1002 } else { 1003 push @headers_paths, $current_dir; 1004 } 1005 1006 foreach my $headers_dir (@headers_paths) { 1007 #calc subdirs 1008 my @subdirs = listSubdirs($headers_dir); 1009 1010 #calc files and "copy" them 1011 foreach my $subdir (@subdirs) { 1012 my @headers = findFiles($subdir, "^[-a-z0-9_]*\\.h\$"); 1013 @headers = grep(!/^qt[a-z0-9]+-config(_p)?\.h$/, @headers); 1014 if (defined $inject_headers{$subdir}) { 1015 foreach my $if (@{$inject_headers{$subdir}}) { 1016 my $cif = $if; 1017 $cif =~ s/^\^//; 1018 @headers = grep(!/^\Q$cif\E$/, @headers); #in case we configure'd previously 1019 push @headers, "*".$if; 1020 } 1021 } 1022 my $header_dirname = ""; 1023 foreach my $header (@headers) { 1024 my $shadow = ($header =~ s/^\*//); 1025 my $no_stamp = $shadow && ($header =~ s/^\^//); 1026 $header = 0 if ($header =~ /^ui_.*\.h$/); 1027 foreach (@ignore_headers) { 1028 $header = 0 if($header eq $_); 1029 } 1030 if($header) { 1031 my $header_copies = 0; 1032 #figure out if it is a public header 1033 my $public_header = $header; 1034 my $qpa_header = 0; 1035 if(isQpaHeader($public_header)) { 1036 $public_header = 0; 1037 $qpa_header = 1; 1038 } elsif ($allheadersprivate || $thisprivate || isPrivateHeader($public_header)) { 1039 $public_header = 0; 1040 } 1041 1042 my $clean_header; 1043 my $requires; 1044 my $iheader_src = $subdir . "/" . $header; 1045 my $iheader = $iheader_src; 1046 $iheader =~ s/^\Q$basedir\E/$build_basedir/ if ($shadow); 1047 if ($check_includes) { 1048 # We need both $public_header and $private_header because QPA headers count as neither 1049 my $private_header = !$public_header && !$qpa_header 1050 && $header =~ /_p\.h$/ && $subdir !~ /3rdparty/; 1051 check_header($lib, $header, $iheader, $public_header, $private_header); 1052 } 1053 my @classes = (); 1054 push @classes, classNames($iheader, \$clean_header, \$requires) 1055 if (!$shadow && $public_header && !$minimal && $is_qt); 1056 my $classname = $classnames{$header}; 1057 push @classes, split(/,/, $classname) if ($classname); 1058 if($showonly) { 1059 print "$header [$lib]\n"; 1060 foreach(@classes) { 1061 print "SYMBOL: $_\n"; 1062 } 1063 } else { 1064 my $ts = $shadow ? 0 : (stat($iheader))[9]; 1065 #find out all the places it goes.. 1066 my $oheader; 1067 if ($public_header) { 1068 $oheader = "$out_basedir/include/$lib/$header"; 1069 foreach my $full_class (@classes) { 1070 my $header_base = basename($header); 1071 # Strip namespaces: 1072 my $class = $full_class; 1073 $class =~ s/^.*:://; 1074 # if ($class =~ m/::/) { 1075 # class =~ s,::,/,g; 1076 # } 1077 1078 $header_copies++ if (!$shadow && syncHeader($lib, "$out_basedir/include/$lib/$class", "$out_basedir/include/$lib/$header", 0, $ts)); 1079 } 1080 } elsif (!$qpa_header) { 1081 $oheader = "$out_basedir/include/$lib/$module_version/$lib/private/$header"; 1082 } else { 1083 $oheader = "$out_basedir/include/$lib/$module_version/$lib/qpa/$header"; 1084 } 1085 $header_copies++ if (!$shadow && syncHeader($lib, $oheader, $iheader, $copy_headers, $ts)); 1086 1087 my $pri_install_iheader = fixPaths($iheader_src, $dir); 1088 my $injection = ""; 1089 if ($public_header) { 1090 foreach my $class (@classes) { 1091 # Strip namespaces: 1092 $class =~ s/^.*:://; 1093# if ($class =~ m/::/) { 1094# $class =~ s,::,/,g; 1095# } 1096 my $class_header = "$class "; 1097 $pri_install_gfiles .= $class_header 1098 unless ($shadow || $pri_install_gfiles =~ m/\b$class_header/); 1099 $injection .= ":$class"; 1100 } 1101 1102 if (!$shadow) { 1103 # put it into the master file 1104 $master_contents{$public_header} = $requires if (shouldMasterInclude($iheader)); 1105 1106 # deal with the install directives 1107 $pri_install_files .= "$pri_install_iheader "; 1108 $pri_clean_files .= "$pri_install_iheader".($requires ? ":".$requires : "")." " if ($clean_header); 1109 } 1110 } 1111 elsif ($qpa_header) { 1112 $pri_install_qpafiles.= "$pri_install_iheader ";; 1113 } 1114 elsif (!$shadow) { 1115 $pri_install_pfiles.= "$pri_install_iheader ";; 1116 } 1117 $pri_injections .= fixPaths($iheader, $build_basedir) 1118 .":".($no_stamp ? "^" : "").fixPaths($oheader, "$out_basedir/include/$lib") 1119 .$injection." " if ($shadow); 1120 } 1121 1122 if ($verbose_level && $header_copies) { 1123 my $new_header_dirname = dirname($iheader); 1124 basePrettify(\$new_header_dirname) if ($new_header_dirname && $verbose_level < 2); 1125 my $header_base = basename($iheader); 1126 if ($verbose_level < 3) { 1127 my $line_prefix = ","; 1128 if ($new_header_dirname ne $header_dirname) { 1129 $line_prefix = "$lib: created fwd-include header(s) for $new_header_dirname/ {"; 1130 $line_prefix = " }\n".$line_prefix if ($header_dirname); 1131 $header_dirname = $new_header_dirname; 1132 } else { 1133 $line_prefix = ","; 1134 } 1135 print "$line_prefix $header_base ($header_copies)"; 1136 } else { # $verbose_level >= 3 1137 basePrettify(\$iheader) if ($verbose_level == 3); 1138 print "$lib: created $header_copies fwd-include headers for $iheader\n"; 1139 } 1140 } 1141 } 1142 } 1143 print " }\n" if ($header_dirname && $verbose_level > 0 && $verbose_level < 3); 1144 } 1145 } 1146 } 1147 1148 # populate the master include: 1149 my $master_contents = 1150 "#ifndef QT_".$libcapitals."_MODULE_H\n" . 1151 "#define QT_".$libcapitals."_MODULE_H\n" . 1152 "#include <$lib/${lib}Depends>\n" . 1153 join("", map { 1154 my $rq = $master_contents{$_}; 1155 ($rq ? "#if QT_CONFIG($rq)\n" : "") . 1156 "#include \"$_\"\n" . 1157 ($rq ? "#endif\n" : "") 1158 } sort globosort keys %master_contents) . 1159 "#include \"".lc($lib)."version.h\"\n" . 1160 "#endif\n"; 1161 1162 unless ($showonly || $minimal || !$is_qt) { 1163 # create deprecated headers 1164 my $first = 1; 1165 while (my ($header, $include) = each %{$deprecatedheaders{$lib}}) { 1166 die "Attempting unsupported aliasing of private header $header.\n" 1167 if ($allheadersprivate || ($header =~ /_p\.h$/)); 1168 1169 my $header_path = "$out_basedir/include/$lib/$header"; 1170 unless (-e $header_path) { 1171 my $guard = "DEPRECATED_HEADER_" . $lib . "_" . $header; 1172 $guard =~ s/([^a-zA-Z0-9_])/_/g; 1173 1174 my $header_dir = dirname($header_path); 1175 make_path($header_dir, $lib, $verbose_level); 1176 1177 my $warning = "Header <$lib/$header> is deprecated. Please include <$include> instead."; 1178 my $hdrcont = 1179 "#ifndef $guard\n" . 1180 "#define $guard\n" . 1181 "#if defined(__GNUC__)\n" . 1182 "# warning $warning\n" . 1183 "#elif defined(_MSC_VER)\n" . 1184 "# pragma message (\"$warning\")\n" . 1185 "#endif\n" . 1186 "#include <$include>\n" . 1187 "#if 0\n" . 1188 "#pragma qt_no_master_include\n" . 1189 "#endif\n" . 1190 "#endif\n"; 1191 if (writeFile($header_path, $hdrcont)) { 1192 if ($verbose_level < 3) { 1193 my $line_prefix = ","; 1194 $line_prefix = "$lib: created deprecated header(s) {" if ($first); 1195 print "$line_prefix $header"; 1196 } else { 1197 print "$lib: created deprecated header $header => $include\n"; 1198 } 1199 $first = 0; 1200 } 1201 } 1202 1203 $pri_install_gfiles .= "$header "; 1204 } 1205 if ($verbose_level < 3) { 1206 print " }\n" unless ($first); 1207 } 1208 1209 # module version header 1210 my $vheader = "$out_basedir/include/$lib/".lc($lib)."version.h"; 1211 my $VHeader = "$out_basedir/include/$lib/${lib}Version"; 1212 syncHeader($lib, $VHeader, $vheader, 0); 1213 $pri_install_gfiles .= lc($lib)."version.h ${lib}Version "; 1214 my @versions = split(/\./, $module_version); 1215 my $modulehexstring = sprintf("0x%02X%02X%02X", $versions[0], $versions[1], $versions[2]); 1216 my $vhdrcont = 1217 "/* This file was generated by syncqt. */\n". 1218 "#ifndef QT_".uc($lib)."_VERSION_H\n". 1219 "#define QT_".uc($lib)."_VERSION_H\n". 1220 "\n". 1221 "#define ".uc($lib)."_VERSION_STR \"".$module_version."\"\n". 1222 "\n". 1223 "#define ".uc($lib)."_VERSION ".$modulehexstring."\n". 1224 "\n". 1225 "#endif // QT_".uc($lib)."_VERSION_H\n"; 1226 writeFile($vheader, $vhdrcont, $lib, "version header"); 1227 1228 my $master_include = "$out_basedir/include/$lib/$lib"; 1229 $pri_install_gfiles .= "$lib "; 1230 writeFile($master_include, $master_contents, $lib, "master header"); 1231 } 1232 1233 unless ($showonly || $minimal) { 1234 #handle the headers.pri for each module 1235 my $headers_pri_contents = ""; 1236 $headers_pri_contents .= "SYNCQT.HEADER_FILES = $pri_install_files\n"; 1237 $headers_pri_contents .= "SYNCQT.GENERATED_HEADER_FILES = $pri_install_gfiles\n"; 1238 $headers_pri_contents .= "SYNCQT.PRIVATE_HEADER_FILES = $pri_install_pfiles\n"; 1239 $headers_pri_contents .= "SYNCQT.QPA_HEADER_FILES = $pri_install_qpafiles\n"; 1240 $headers_pri_contents .= "SYNCQT.CLEAN_HEADER_FILES = $pri_clean_files\n"; 1241 $headers_pri_contents .= "SYNCQT.INJECTIONS = $pri_injections\n"; 1242 my $headers_pri_file = "$out_basedir/include/$lib/headers.pri"; 1243 writeFile($headers_pri_file, $headers_pri_contents, $lib, "headers.pri file"); 1244 } 1245} 1246 1247exit 0; 1248